Mod:CMake使用

来自Noita Wiki
跳到导航 跳到搜索
模组制作导航
基础
入门基础Lua脚本Data.wak实用工具
制作指南
音频敌人生物群系天赋法术精灵表材料图像放射器特殊行为CMake使用
组件/实体
组件文档枚举特殊标签所有标签列表
Lua编程
Lua API实用脚本
其他信息
法术和天赋的ID声音事件魔数(Magic Numbers)

CMake是一个主要针对C和c++开发的构建系统 但它也可以用于许多其他事情,包括自动化Noita模型开发过程中的步骤。

在你有例如以下高级需求之前你并没有什么必须要使用它的理由

  • 创建发布zip文件
  • 下载文件
  • 生成文件
  • 编译本机代码
  • 运行测试

本指南假定具有CMD/PowerShell和Noita模组开发的基本经验。 有使用CMake的经验也会有帮助。

安装

参见:

确保 CMake 和 Ninja 在PATH环境变量中。

基础

项目开始

Let's create a basic mod that uses CMake to automate some steps. The mod name is "HelloMod", this is the project structure: 创建一个基本mod, 由CMake自动执行一些步骤。 此mod名为"HelloMod" 这是项目结构:

HelloMod-source/
├── CMakeLists.txt
└── HelloMod
    ├── init.lua
    └── mod.xml

此mod仅作为演示,init.lua 与 mod.xml的内容并不重要。

Files that should be copied verbatim to the final mod folder are put in a subdirectory with the same name as the desired mod folder,(暂时保留原文) 需要被复制到最终mod目录中的文件要被置于一个与最终mod目录同名的一个子目录中 此处必须包含init.luamod.xml 不应该包含在发行版中或必须以某种方式生成/处理的文件放在此目录之外。

CMakeLists.txt 用来指定CMake应该做什么 这是我们如何定义一个基本可安装的项目:

cmake_minimum_required(VERSION 3.24)

project(HelloMod
    VERSION 0.1.0
    DESCRIPTION "Noita example mod"
    HOMEPAGE_URL "https://github.com/dextercd/Noita-CMake-Example"
    LANGUAGES # Empty
)

install(DIRECTORY HelloMod
    DESTINATION .
    COMPONENT HelloMod
)
  • cmake_minimum_required specifies what version of CMake we want to use
  • project lists some basic info about the project
  • install(DIRECTORY ...) makes it so that the directory is copied to the install location when we run the installation step

这些事构建和安装mod的CMake命令:

PS Y:\> cmake -G Ninja -DCMAKE_INSTALL_PREFIX="C:\Program Files (x86)\Steam\steamapps\common\Noita\mods" -B Y:\HelloMod-build -S Y:\HelloMod-source
-- Configuring done
-- Generating done
-- Build files have been written to: Y:/HelloMod-build
PS Y:\> cd Y:\HelloMod-build
PS Y:\HelloMod-build> cmake --build .
ninja: no work to do.
PS Y:\HelloMod-build> cmake --install .  --component HelloMod
-- Install configuration: ""
-- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod
-- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod/init.lua
-- Installing: C:/Program Files (x86)/Steam/steamapps/common/Noita/mods/./HelloMod/mod.xml

通过使用第一个CMake命令来配置构建 它能通过这种方式获得源目录的位置以及构建输出的位置 剩余部分假设你正在运行构建目录下的CMake命令。.

——build子命令检测对CMakeLists.txt所做的更改,以便创建更新的构建文件。 在此之后,它构建目标(目前我们还没有目标)。 You can use the --install subcommand to install the files to the CMAKE_INSTALL_PREFIX path that was specified in the configure step. Using your Noita mods folder for this makes a lot of sense when you are developing your mod.(暂时保留原文) 您可以使用——install子命令将文件安装到在配置步骤中指定的CMAKE_INSTALL_PREFIX路径中。 当你在开发mod时,使用你的Noita mod文件夹是很有意义的。

创建包

This basic CMake project was pretty simple to setup, but we didn't gain anything compared to just working directly in our Noita mods folder. Let's use CMake's packaging functionality to do something useful and automate the creation of a mod release zip file.

Packaging is handled by CPack which is included with CMake, using it requires some boilerplate code:

# ... previous CMakeLists.txt content is above here ...

# Packaging

set(CPACK_GENERATOR ZIP)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY FALSE)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(CPACK_VERBATIM_VARIABLES TRUE)
include(CPack)
  • CPACK_GENERATOR: list of default package generators to use, in this case only ZIP. Other options include 7Z (.7z), TGZ (.tar.gz), TBZ2 (.tar.bz2), and more.
  • CPACK_INCLUDE_TOPLEVEL_DIRECTORY: by default CPack adds an additional directory in the archive. Here we don't want that.
  • CPACK_PACKAGE_FILE_NAME: normally the system you're building for is included in the package name, e.g. 'Linux' or 'win32'. We don't want this so we change it from the default.
  • CPACK_VERBATIM_VARIABLES: this is false by default for backwards compatibility but in new projects you want to set this to true.

After adding these lines you can use CPack to create a zip file of your mod:

PS Y:\HelloMod-build> cmake --build .
[0/1] Re-running CMake...-- Configuring done
-- Generating done
-- Build files have been written to: Y:/HelloMod-build

ninja: no work to do.
PS Y:\HelloMod-build> cpack
CPack: Create package using ZIP
CPack: Install projects
CPack: - Install project: HelloMod []
CPack: Create package
CPack: - package: Y:/HelloMod-build/HelloMod-0.1.0.zip generated.

Tada! If you want to make a 7Zip archive you just run cpack -G 7Z instead. Archives built this way have the following structure:

Archive:  HelloMod-0.1.0.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2022-12-29 20:53   HelloMod/
       77  2022-12-28 21:23   HelloMod/init.lua
       80  2022-12-28 21:55   HelloMod/mod.xml

It has a top-level folder with the name we chose, to install the mod the user can open the zip then drag and drop this into their mods folder.

Downloading Files

Sometimes we want to include code or other files from some other project into our mod. A lot of the time the simplest/best solution is to copy the files into your repository, but if there are many files, large files, or binary files (e.g. .dll, .exe) then you may not want to do that. You can instead use CMake to manage these dependencies.

Let's say you want to use EZWand and LuaJIT-MinHook in your mod, this is what you would add to the CMakeLists.txt file:

# ... basic project setup is above here ...

# Dependencies

include(FetchContent)

FetchContent_Declare(EZWand
    URL https://github.com/TheHorscht/EZWand/releases/download/v1.5.0/EZWand.lua
    DOWNLOAD_NO_EXTRACT TRUE
)
FetchContent_MakeAvailable(EZWand)

install(FILES ${ezwand_SOURCE_DIR}/EZWand.lua
    DESTINATION HelloMod/lib/EZWand
    COMPONENT HelloMod
)

FetchContent_Declare(LuaJIT-MinHook
    URL https://github.com/dextercd/LuaJIT-MinHook/releases/download/release-1.1.1/LuaJIT-MinHook-1.1.1.zip
)
FetchContent_MakeAvailable(LuaJIT-MinHook)

install(DIRECTORY ${luajit-minhook_SOURCE_DIR}/
    DESTINATION HelloMod/lib/MinHook
    COMPONENT HelloMod
)

# ... CPack stuff should always come last ...

The FetchContent module is used to download files/archives.

We specify the name and download location of dependencies using FetchContent_Declare. EZWand is a single Lua file, not an archive that must be extracted, so we must specify the DOWNLOAD_NO_EXTRACT TRUE option.

FetchContent_MakeAvailable will do the initial download and perform a redownload whenever we change the URL. (This means updating dependencies should be pretty simple!)

After the FetchContent_MakeAvailable command has run it creates a <lowercaseName>_SOURCE_DIR variable which is the path the files were downloaded to. We use this to install the files we want to include in the mod.

After re-running cmake --build . and cpack we have a zip archive containing these files:

Archive:  HelloMod-0.1.0.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2022-12-29 21:49   HelloMod/
        0  2022-12-29 21:49   HelloMod/lib/
        0  2022-12-29 21:49   HelloMod/lib/MinHook/
     3346  2022-12-29 21:33   HelloMod/lib/MinHook/minhook.lua
    21504  2022-12-29 21:33   HelloMod/lib/MinHook/luajit_minhook.dll
        0  2022-12-29 21:49   HelloMod/lib/EZWand/
    44068  2022-12-29 20:53   HelloMod/lib/EZWand/EZWand.lua
       77  2022-12-28 21:23   HelloMod/init.lua
       80  2022-12-28 21:55   HelloMod/mod.xml
---------                     -------
    69075                     9 files

You can still use cmake --install . --component HelloMod to place the files in your mod folder during development.

Building C/C++ Code

CMake is primarily a C and C++ build system so it's ideal for building executables and DLLs for your mod. Whether this native code is for testing parts of your mod, generating data, or an integral part of how your mod works, CMake can compile, execute, and package these files for you.

In this guide we will use these facilities to build a DLL with which we can set the contents of the clipboard. The mod will use this to put the world seed in the player's clipboard at the start of a run.

To start you need a C/C++ toolchain installed (most likely some version of VC++). Make sure CMake can find this toolchain, with VC++ you need to make it visible in the environment by launching an 'x86 Native Tools Command Prompt'.

Now we need to tell CMake that our project uses C++, we do this by changing the project command in the CMakeLists.txt file.

 project(HelloMod
     VERSION 0.1.0
     DESCRIPTION "Noita example mod"
     HOMEPAGE_URL "https://github.com/dextercd/Noita-CMake-Example"
+    LANGUAGES CXX
 )

If you run the --build subcommand now it should show info about the compilation tools CMake found.

The project has a clipboard.cpp file in the root directory. This C++ code exports a single function that we can use from Lua which sets the contents of the clipboard using the win32 API.

Now we tell CMake to build this file into a DLL and package it with the rest of the mod. Make sure this code is added above the packaging setup.

# ...

# C++ module

add_library(clipboard MODULE
    clipboard.cpp
)

install(TARGETS clipboard
    LIBRARY DESTINATION HelloMod/dll
    COMPONENT HelloMod
)

# ... CPack stuff should always come last ...

If you build and install the project you should see clipboard.dll in your mod inside of the dll folder.

Now enable request_no_api_restrictions="1" in mod.xml, and change the init.lua file to this:

local ffi = require("ffi")

ffi.cdef([[
bool set_clipboard(const char* text);
]])

local clipboard = ffi.load("mods/HelloMod/dll/clipboard.dll")

function OnWorldInitialized()
    local seed = StatsGetValue("world_seed")
    local text = "Currently playing this seed: " .. seed
    if not clipboard.set_clipboard(text) then
        GamePrint("Couldn't set clipboard!")
    end
end

And that should be everything! You now have a functioning mod that puts the current seed you're playing on in your clipboard. Installing and packaging using CMake should work exactly like before.

Note that this example uses LuaJIT's ffi module instead of a C package that interfaces with Lua using the Lua C API. The latter requires finding and linking against Lua which is a lot more tedious to setup (but definitely possible!)

Project Using CMake

Links to Noita mods that use CMake, perhaps there are some useful snippets that you can reuse:

Resources

This guide is here just to demonstrate some useful techniques, and to have some code that you can copy and paste to get a project started on CMake. It's not a replacement for CMake's documentation or other learning material.