内置模块
在自定义脚本、插件脚本、任务脚本、平台扩展、模板扩展等脚本代码中使用,也就是在类似下面的代码块中,可以使用这些模块接口:
on_run(function (target)
print("hello xmake!")
end)
为了保证外层的描述域尽可能简洁、安全,一般不建议在这个域使用接口和模块操作 api,因此大部分模块接口只能脚本域使用,来实现复杂功能。
当然少部分只读的内置接口还是可以在描述域使用的,具体见下表:
TODO。
在描述域使用接口调用的实例如下,一般仅用于条件控制:
-- 扫描当前 xmake.lua 目录下的所有子目录,以每个目录的名字定义一个 task 任务
for _, taskname in ipairs(os.dirs("*"), path.basename) do
task(taskname, function()
on_run(function ()
end)
end)
end
上面所说的脚本域、描述域主要是指:
-- 描述域
target("test", function()
-- 描述域
set_kind("static")
add_files("src/*.c")
on_run(function (target)
-- 脚本域
end)
end)
-- 描述域
import
导入扩展摸块
import 的主要用于导入 xmake 的扩展类库以及一些自定义的类库模块,一般用于:
导入机制如下:
- 优先从当前脚本目录下导入
- 再从扩展类库中导入
导入的语法规则:
基于 .
的类库路径规则,例如:
import("core.base.option")
import("core.base.task")
function main()
-- 获取参数选项
print(option.get("version"))
-- 运行任务和插件
task.run("hello")
end
导入当前目录下的自定义模块:
目录结构:
plugin
- xmake.lua
- main.lua
- modules
- hello1.lua
- hello2.lua
在 main.lua 中导入 modules
import("modules.hello1")
import("modules.hello2")
导入后就可以直接使用里面的所有公有接口,私有接口用 _
前缀标示,表明不会被导出,不会被外部调用到。
除了当前目录,我们还可以导入其他指定目录里面的类库,例如:
import("hello3", {rootdir = "/home/xxx/modules"})
为了防止命名冲突,导入后还可以指定的别名:
import("core.platform.platform", {alias = "p"})
function main()
-- 这样我们就可以使用 p 来调用 platform 模块的 plats 接口,获取所有 xmake 支持的平台列表了
utils.dump(p.plats())
end
import 不仅可以导入类库,还支持导入的同时作为继承导入,实现模块间的继承关系
import("xxx.xxx", {inherit = true})
这样导入的不是这个模块的引用,而是导入的这个模块的所有公有接口本身,这样就会跟当前模块的接口进行合并,实现模块间的继承。
另外还支持两个新属性:import("xxx.xxx", {try = true, anonymous = true})
- try 为 true,则导入的模块不存在的话,仅仅返回 nil,并不会抛异常后中断 xmake
- anonymous 为 true,则导入的模块不会引入当前作用域,仅仅在 import 接口返回导入的对象引用
自定义扩展模块
通过 import 我们除了可以导入 xmake 内置的很多扩展模块,还可以导入用户自己定义的扩展模块。
只需要将自己的模块放到工程目录下,按照上文介绍的导入方式进行导入即可。
那么,如果去定义模块呢?xmake 对模块的编写规范是有一套约定规则的,并没有沿用 lua 原生的 require 导入机制,并不需要在模块中使用 return 来全局返回它。
假如我们有一个模块文件 foo.lua,它的内容如下:
function _foo(a, b)
return a + b
end
function add(a, b)
_foo(a, b)
end
function main(a, b)
add(a, b)
end
其中 main 为入口函数,可选,如果设置,模块 foo 可以直接被调用,例如:
import("foo")
foo(1, 2)
或者直接这样:
import("foo")(1, 2)
其他不带下划线的为 public 模块接口函数,例如 add。
import("foo")
foo.add(1, 2)
而里面带下划线前缀的 _foo
是私有函数,模块内部使用,不对外导出,所以在外面用户是不能够调用它的。
inherit
导入并继承基类模块。
这个等价于 import 接口的 inherit
模式,也就是:
import("xxx.xxx", {inherit = true})
用 inherit
接口的话,会更简洁些:
inherit("xxx.xxx")
使用实例,可以参看 xmake 的 tools 目录下的脚本:clang.lua
这个就是 clang 工具模块继承了 gcc 的部分实现。
try-catch-finally
异常捕获。
lua 原生并没有提供 try-catch 的语法来捕获异常处理,但是提供了 pcall/xpcall
等接口,可在保护模式下执行 lua 函数。
因此,可以通过封装这两个接口,来实现 try-catch 块的捕获机制。
我们可以先来看下,封装后的 try-catch 使用方式:
try
{
-- try 代码块
function ()
error("error message")
end,
-- catch 代码块
catch
{
-- 发生异常后,被执行
function (errors)
print(errors)
end
}
}
上面的代码中,在 try 块内部认为引发了一个异常,并且抛出错误消息,在 catch 中进行了捕获,并且将错误消息进行输出显示。
而 finally 的处理,这个的作用是对于 try{}
代码块,不管是否执行成功,都会执行到 finally 块中
也就说,其实上面的实现,完整的支持语法是:try-catch-finally
模式,其中 catch 和 finally 都是可选的,根据自己的实际需求提供
例如:
try
{
-- try 代码块
function ()
error("error message")
end,
-- catch 代码块
catch
{
-- 发生异常后,被执行
function (errors)
print(errors)
end
},
-- finally 代码块
finally
{
-- 最后都会执行到这里
function (ok, errors)
-- 如果 try{} 中存在异常,ok 为 true,errors 为错误信息,否则为 false,errors 为 try 中的返回值
end
}
}
或者只有 finally 块:
try
{
-- try 代码块
function ()
return "info"
end,
-- finally 代码块
finally
{
-- 由于此 try 代码没发生异常,因此 ok 为 true,errors 为返回值: "info"
function (ok, errors)
end
}
}
处理可以在 finally 中获取 try 里面的正常返回值,其实在仅有 try 的情况下,也是可以获取返回值的:
-- 如果没发生异常,result 为返回值:"xxxx",否则为 nil
local result = try
{
function ()
return "xxxx"
end
}
在 xmake 的自定义脚本、插件开发中,也是完全基于此异常捕获机制。
这样使得扩展脚本的开发非常的精简可读,省去了繁琐的 if err ~= nil then
返回值判断,在发生错误时,xmake 会直接抛出异常进行中断,然后高亮提示详细的错误信息。
例如:
target("test", function()
set_kind("binary")
add_files("src/*.c")
-- 在编译完 ios 程序后,对目标程序进行 ldid 签名
after_build(function (target))
os.run("ldid -S %s", target:targetfile())
end
end)
只需要一行 os.run
就行了,也不需要返回值判断是否运行成功,因为运行失败后,xmake 会自动抛异常,中断程序并且提示错误。
如果你想在运行失败后,不直接中断 xmake,继续往下运行,可以自己加个 try 快就行了:
target("test", function()
set_kind("binary")
add_files("src/*.c")
after_build(function (target))
try
{
function ()
os.run("ldid -S %s", target:targetfile())
end
}
end
end)
如果还想捕获出错信息,可以再加个 catch:
target("test", function()
set_kind("binary")
add_files("src/*.c")
after_build(function (target))
try
{
function ()
os.run("ldid -S %s", target:targetfile())
end,
catch
{
function (errors)
print(errors)
end
}
}
end
end)
不过一般情况下,在 xmake 中写自定义脚本,是不需要手动加 try-catch 的,直接调用各种 api,出错后让 xmake 默认的处理程序接管,直接中断就行了。
pairs
用于遍历字典。
这个是 lua 原生的内置 api,在 xmake 中,在原有的行为上对其进行了一些扩展,来简化一些日常的 lua 遍历代码。
先看下默认的原生写法:
local t = {a = "a", b = "b", c = "c", d = "d", e = "e", f = "f"}
for key, val in pairs(t) do
print("%s: %s", key, val)
end
这对于通常的遍历操作就足够了,但是如果我们相对其中每个遍历出来的元素,获取其大写,我们可以这么写:
for key, val in pairs(t, function (v) return v:upper() end) do
print("%s: %s", key, val)
end
甚至传入一些参数到第二个 function
中,例如:
for key, val in pairs(t, function (v, a, b) return v:upper() .. a .. b end, "a", "b") do
print("%s: %s", key, val)
end
ipairs
用于遍历数组。
这个是 lua 原生的内置 api,在 xmake 中,在原有的行为上对其进行了一些扩展,来简化一些日常的 lua 遍历代码。
先看下默认的原生写法:
for idx, val in ipairs({"a", "b", "c", "d", "e", "f"}) do
print("%d %s", idx, val)
end
扩展写法类似 pairs 接口,例如:
for idx, val in ipairs({"a", "b", "c", "d", "e", "f"}, function (v) return v:upper() end) do
print("%d %s", idx, val)
end
for idx, val in ipairs({"a", "b", "c", "d", "e", "f"}, function (v, a, b) return v:upper() .. a .. b end, "a", "b") do
print("%d %s", idx, val)
end
这样可以简化 for
块代码的逻辑,例如我要遍历指定目录,获取其中的文件名,但不包括路径,就可以通过这种扩展方式,简化写法:
for _, filename in ipairs(os.dirs("*"), path.filename) do
-- ...
end
换行打印终端日志。
此接口也是 lua 的原生接口,xmake 在原有行为不变的基础上也进行了扩展,同时支持:格式化输出、多变量输出。
先看下原生支持的方式:
print("hello xmake!")
print("hello", "xmake!", 123)
并且同时还支持扩展的格式化写法:
print("hello %s!", "xmake")
print("hello xmake! %d", 123)
xmake 会同时支持这两种写法,内部会去自动智能检测,选择输出行为。
printf
无换行打印终端日志。
类似 print 接口,唯一的区别就是不换行。
cprint
换行彩色打印终端日志。
行为类似 print,区别就是此接口还支持彩色终端输出,并且支持 emoji
字符输出。
例如:
cprint('${bright}hello xmake')
cprint('${red}hello xmake')
cprint('${bright green}hello ${clear}xmake')
cprint('${blue onyellow underline}hello xmake${clear}')
cprint('${red}hello ${magenta}xmake')
cprint('${cyan}hello ${dim yellow}xmake')
显示结果如下:
跟颜色相关的描述,都放置在 ${ }
里面,可以同时设置多个不同的属性,例如:
${bright red underline onyellow}
表示:高亮红色,背景黄色,并且带下滑线
所有这些描述,都会影响后面一整行字符,如果只想显示部分颜色的文字,可以在结束位置,插入 ${clear}
清楚前面颜色描述
例如:
${red}hello ${clear}xmake
这样的话,仅仅 hello 是显示红色,其他还是正常默认黑色显示。
其他颜色属于,我这里就不一一介绍,直接贴上 xmake 代码里面的属性列表吧:
colors.keys =
{
-- 属性
reset = 0 -- 重置属性
, clear = 0 -- 清楚属性
, default = 0 -- 默认属性
, bright = 1 -- 高亮
, dim = 2 -- 暗色
, underline = 4 -- 下划线
, blink = 5 -- 闪烁
, reverse = 7 -- 反转颜色
, hidden = 8 -- 隐藏文字
-- 前景色
, black = 30
, red = 31
, green = 32
, yellow = 33
, blue = 34
, magenta = 35
, cyan = 36
, white = 37
-- 背景色
, onblack = 40
, onred = 41
, ongreen = 42
, onyellow = 43
, onblue = 44
, onmagenta = 45
, oncyan = 46
, onwhite = 47
}
除了可以色彩高亮显示外,如果你的终端是在 macosx 下,lion 以上的系统,xmake 还可以支持 emoji 表情的显示哦,对于不支持系统,会忽略显示,例如:
cprint("hello xmake${beer}")
cprint("hello${ok_hand} xmake")
上面两行代码,我打印了一个 homebrew 里面经典的啤酒符号,下面那行打印了一个 ok 的手势符号:
所有的 emoji 表情,以及 xmake 里面对应的 key,都可以通过 emoji 符号 里面找到。
同时支持 24 位真彩色输出,如果终端支持的话:
import("core.base.colors")
if colors.truecolor() then
cprint("${255;0;0}hello")
cprint("${on;255;0;0}hello${clear} xmake")
cprint("${bright 255;0;0 underline}hello")
cprint("${bright on;255;0;0 0;255;0}hello${clear} xmake")
end
xmake 对于 truecolor 的检测支持,是通过 $COLORTERM
环境变量来实现的,如果你的终端支持 truecolor,可以手动设置此环境变量,来告诉 xmake 启用 truecolor 支持。
可以通过下面的命令来启用和测试:
$ export COLORTERM=truecolor
$ xmake --version
可通过 COLORTERM=nocolor
来禁用色彩输出。
cprintf
无换行彩色打印终端日志。
此接口类似 cprint,区别就是不换行输出。
format
格式化字符串。
如果只是想格式化字符串,不进行输出,可以使用这个接口,此接口跟 string.format 接口等价,只是个接口名简化版。
local s = format("hello %s", xmake)
vformat
格式化字符串,支持内置变量转义。
此接口跟 format 接口类似,只是增加对内置变量的获取和转义支持。
local s = vformat("hello %s $(mode) $(arch) $(env PATH)", xmake)
raise
抛出异常中断程序。
如果想在自定义脚本、插件任务中中断 xmake 运行,可以使用这个接口抛出异常,如果上层没有显示调用 try-catch 捕获的话,xmake 就会中断执行,并且显示出错信息。
if (errors) raise(errors)
如果在 try 块中抛出异常,就会在 catch 和 finally 中进行 errors 信息捕获,具体见:try-catch
os
系统操作模块,属于内置模块,无需使用 import 导入,可直接脚本域调用其接口。
此模块也是 lua 的原生模块,xmake 在其基础上进行了扩展,提供更多实用的接口。
os 模块里面只有部分 readonly 接口(例如:
os.getenv
,os.arch
)是可以在描述域中使用,其他接口只能在脚本域中使用,例如:os.cp
,os.rm
等
接口 | 描述 | 支持版本 |
---|---|---|
os.cp | 复制文件或目录 | >= 2.0.1 |
os.mv | 移动重命名文件或目录 | >= 2.0.1 |
os.rm | 删除文件或目录树 | >= 2.0.1 |
os.trycp | 尝试复制文件或目录 | >= 2.1.6 |
os.trymv | 尝试移动重命名文件或目录 | >= 2.1.6 |
os.tryrm | 尝试删除文件或目录树 | >= 2.1.6 |
os.cd | 进入指定目录 | >= 2.0.1 |
os.rmdir | 删除目录树 | >= 2.0.1 |
os.mkdir | 创建指定目录 | >= 2.0.1 |
os.isdir | 判断目录是否存在 | >= 2.0.1 |
os.isfile | 判断文件是否存在 | >= 2.0.1 |
os.exists | 判断文件或目录是否存在 | >= 2.0.1 |
os.dirs | 遍历获取指定目录下的所有目录 | >= 2.0.1 |
os.files | 遍历获取指定目录下的所有文件 | >= 2.0.1 |
os.filedirs | 遍历获取指定目录下的所有文件或目录 | >= 2.0.1 |
os.run | 安静运行程序 | >= 2.0.1 |
os.runv | 安静运行程序,带参数列表 | >= 2.1.5 |
os.exec | 回显运行程序 | >= 2.0.1 |
os.execv | 回显运行程序,带参数列表 | >= 2.1.5 |
os.iorun | 运行并获取程序输出内容 | >= 2.0.1 |
os.iorunv | 运行并获取程序输出内容,带参数列表 | >= 2.1.5 |
os.getenv | 获取环境变量 | >= 2.0.1 |
os.setenv | 设置环境变量 | >= 2.0.1 |
os.tmpdir | 获取临时目录路径 | >= 2.0.1 |
os.tmpfile | 获取临时文件路径 | >= 2.0.1 |
os.curdir | 获取当前目录路径 | >= 2.0.1 |
os.filesize | 获取文件大小 | >= 2.1.9 |
os.scriptdir | 获取脚本目录路径 | >= 2.0.1 |
os.programdir | 获取 xmake 安装主程序脚本目录 | >= 2.1.5 |
os.programfile | 获取 xmake 可执行文件路径 | >= 2.1.5 |
os.projectdir | 获取工程主目录 | >= 2.1.5 |
os.arch | 获取当前系统架构 | >= 2.0.1 |
os.host | 获取当前主机系统 | >= 2.0.1 |
os.subhost | 获取子系统 | >= 2.3.1 |
os.subarch | 获取子系统架构 | >= 2.3.1 |
os.is_host | 判断给定系统是否正确 | >= 2.3.1 |
os.is_arch | 判断给定架构是否正确 | >= 2.3.1 |
os.is_subhost | 判断给定子系统是否正确 | >= 2.3.1 |
os.is_subarch | 判断子系统架构是否正确 | >= 2.3.1 |
os.ln | 创建指向文件或文件夹的符号链接 | >= 2.2.2 |
os.readlink | 读取符号链接 | >= 2.2.2 |
os.raise | 抛出一个异常并中止脚本运行 | >= 2.2.8 |
os.raiselevel | 抛出一个异常并中止脚本运行 | >= 2.2.8 |
os.features | 获取系统特性 | >= 2.3.1 |
os.getenvs | 获取所有环境变量 | >= 2.2.6 |
os.setenvs | 替换当前所有环境变量 | >= 2.2.6 |
os.addenvs | 向当前环境变量中添加新值 | >= 2.5.6 |
os.joinenvs | 拼接环境变量 | >= 2.5.6 |
os.setenvp | 使用给定分隔符设置环境变量 | >= 2.1.5 |
os.addenvp | 使用给定分隔符向环境变量添加新值 | >= 2.1.5 |
os.workingdir | 获取工作路径 | >= 2.1.9 |
os.isroot | 判断当前 xmake 是否以管理员权限运行 | >= 2.1.9 |
os.fscase | 判断当前系统的文件系统是否大小写敏感 | >= 2.1.9 |
os.term | 获取当前终端 | >= 2.7.3 |
os.shell | 获取当前 shell | >= 2.7.3 |
os.cpuinfo | 获取 CPU 信息 | >= 2.1.5 |
os.meminfo | 获取内存信息 | >= 2.1.5 |
os.default_njob | 获取默认编译任务数 | >= 2.5.8 |
os.cp
复制文件或目录。
行为和 shell 中的 cp
命令类似,支持路径通配符匹配(使用的是 lua 模式匹配),支持多文件复制,以及内置变量支持。
例如:
os.cp("$(scriptdir)/*.h", "$(buildir)/inc")
os.cp("$(projectdir)/src/test/**.h", "$(buildir)/inc")
上面的代码将:当前 xmake.lua
目录下的所有头文件、工程源码 test 目录下的头文件全部复制到 $(buildir)
输出目录中。
其中 $(scriptdir)
, $(projectdir)
这些变量是 xmake 的内置变量,具体详情见:[内置变量](# 内置变量) 的相关文档。
而 *.h
和 **.h
中的匹配模式,跟 add_files 中的类似,前者是单级目录匹配,后者是递归多级目录匹配。
此接口同时支持目录的 递归复制
,例如:
-- 递归复制当前目录到临时目录
os.cp("$(curdir)/test/", "$(tmpdir)/test")
上面的复制,会把所有文件全部展开复制到指定目录,丢失源目录层级,如果要按保持原有的目录结构复制,可以设置 rootdir 参数:
os.cp("src/**.h", "/tmp/", {rootdir = "src"})
上面的脚本可以按 src
根目录,将 src 下的所有子文件保持目录结构复制过去。
尽量使用
os.cp
接口,而不是os.run("cp ..")
,这样更能保证平台一致性,实现跨平台构建描述。
同时提供了 {symlink = true}
参数,在复制文件时候保留符号链接。
os.cp("/xxx/foo", "/xxx/bar", {symlink = true})
和 Linux 上的 cp 命令不同,xmake 的 cp 接口无法将同名的文件夹复制到目标路径,最终只能保留一个文件夹。例如:
文件路径,A 和 B 有一个同名的文件夹 conf:
.
├── A
│ └── conf
│ ├── 1
│ │ └── A1.txt
│ └── 2
│ └── A2.txt
├── B
│ └── conf
│ ├── 1
│ │ └── B1.txt
│ └── 2
│ └── B2.txt
└── test.lua
我们希望将 A 和 B 两个 conf 文件夹的内容都合并到 output 目录下:
os.mkdir("output/test1")
os.cp("A/conf", "output/test1", {rootdir = "A/conf"})
os.cp("B/conf", "output/test1", {rootdir = "B/conf"})
合并完后丢失 A 的 conf 信息:
$tree output/
output/
└── test1
├── 1
│ └── B1.txt
└── 2
└── B2.txt
最终我们不得已通过 bash -c
实现这个功能:
os.mkdir("output/test5")
os.vrun("bash -c 'cp -r A/conf/* output/test5'")
os.vrun("bash -c 'cp -r B/conf/* output/test5'")
相关的讨论可见 xmake 源码的 example/api/os.cp
例子。
os.mv
移动重命名文件或目录。
跟 os.cp 的使用类似,同样支持多文件移动操作和模式匹配,例如:
-- 移动文件到临时目录
os.mv("$(buildir)/test1", "$(tmpdir)")
-- 文件移动不支持批量操作,也就是文件重命名
os.mv("$(buildir)/libtest.a", "$(buildir)/libdemo.a")
os.rm
删除文件或目录树。
支持递归删除目录,批量删除操作,以及模式匹配和内置变量,例如:
os.rm("$(buildir)/inc/**.h")
os.rm("$(buildir)/lib/")
os.trycp
尝试复制文件或目录。
跟 os.cp 类似,唯一的区别就是,此接口操作失败不会抛出异常中断 xmake,而是通过返回值标示是否执行成功。
if os.trycp("file", "dest/file") then
end
os.trymv
尝试移动文件或目录。
跟 os.mv 类似,唯一的区别就是,此接口操作失败不会抛出异常中断 xmake,而是通过返回值标示是否执行成功。
if os.trymv("file", "dest/file") then
end
os.tryrm
尝试删除文件或目录。
跟 os.rm 类似,唯一的区别就是,此接口操作失败不会抛出异常中断 xmake,而是通过返回值标示是否执行成功。
if os.tryrm("file") then
end
os.cd
进入指定目录。
这个操作用于目录切换,同样也支持内置变量,但是不支持模式匹配和多目录处理,例如:
-- 进入临时目录
os.cd("$(tmpdir)")
如果要离开进入之前的目录,有多种方式:
-- 进入上级目录
os.cd("..")
-- 进入先前的目录,相当于:cd -
os.cd("-")
-- 进入目录前保存之前的目录,用于之后跨级直接切回
local oldir = os.cd("./src")
...
os.cd(oldir)
os.rmdir
仅删除目录。
如果不是目录就无法删除。
os.mkdir
创建目录。
支持批量创建和内置变量,例如:
os.mkdir("$(tmpdir)/test", "$(buildir)/inc")
os.isdir
判断是否为目录。
如果目录不存在,则返回 false
if os.isdir("src") then
-- ...
end
os.isfile
判断是否为文件。
如果文件不存在,则返回 false
if os.isfile("$(buildir)/libxxx.a") then
-- ...
end
os.exists
判断文件或目录是否存在。
如果文件或目录不存在,则返回 false
-- 判断目录存在
if os.exists("$(buildir)") then
-- ...
end
-- 判断文件存在
if os.exists("$(buildir)/libxxx.a") then
-- ...
end
os.dirs
遍历获取指定目录下的所有目录。
支持 add_files 中的模式匹配,支持递归和非递归模式遍历,返回的结果是一个 table 数组,如果获取不到,返回空数组,例如:
-- 递归遍历获取所有子目录
for _, dir in ipairs(os.dirs("$(buildir)/inc/**")) do
print(dir)
end
os.files
遍历获取指定目录下的所有文件。
支持 add_files 中的模式匹配,支持递归和非递归模式遍历,返回的结果是一个 table 数组,如果获取不到,返回空数组,例如:
-- 非递归遍历获取所有子文件
for _, filepath in ipairs(os.files("$(buildir)/inc/*.h")) do
print(filepath)
end
os.filedirs
遍历获取指定目录下的所有文件和目录。
支持 add_files 中的模式匹配,支持递归和非递归模式遍历,返回的结果是一个 table 数组,如果获取不到,返回空数组,例如:
-- 递归遍历获取所有子文件和目录
for _, filedir in ipairs(os.filedirs("$(buildir)/**")) do
print(filedir)
end
os.run
安静运行原生 shell 命令。
用于执行第三方的 shell 命令,但不会回显输出,仅仅在出错后,高亮输出错误信息。
此接口支持参数格式化、内置变量,例如:
-- 格式化参数传入
os.run("echo hello %s!", "xmake")
-- 列举构建目录文件
os.run("ls -l $(buildir)")
使用此接口执行 shell 命令,容易使构建跨平台性降低,对于
os.run("cp ..")
这种尽量使用os.cp
代替。如果必须使用此接口运行 shell 程序,请自行使用 config.plat 接口判断平台支持。
os.runv
安静运行原生 shell 命令,带参数列表。
跟 os.run 类似,只是传递参数的方式是通过参数列表传递,而不是字符串命令,例如:
os.runv("echo", {"hello", "xmake!"})
另外,此接口也支持 envs 参数设置:
os.runv("echo", {"hello", "xmake!"}, {envs = {PATH = "xxx;xx", CFLAGS = "xx"}})
os.exec
回显运行原生 shell 命令。
与 os.run 接口类似,唯一的不同是,此接口执行 shell 程序时,是带回显输出的,一般调试的时候用的比较多
os.execv
回显运行原生 shell 命令,带参数列表。
跟 os.exec 类似,只是传递参数的方式是通过参数列表传递,而不是字符串命令,例如:
os.execv("echo", {"hello", "xmake!"})
另外,此接口还支持一个可选的参数,用于传递设置:重定向输出,执行环境变量设置,例如:
os.execv("echo", {"hello", "xmake!"}, {stdout = outfile, stderr = errfile, envs = {PATH = "xxx;xx", CFLAGS = "xx"}}
其中,stdout 和 stderr 参数用于传递重定向输出和错误输出,可以直接传入文件路径,也可以传入 io.open 打开的文件对象。
xmake 还支持设置 stdin 参数,来支持重定向输入文件。
stdout/stderr/stdin 可以同时支持:文件路径、文件对象、管道对象等三种类型值。
另外,如果想在这次执行中临时设置和改写一些环境变量,可以传递 envs 参数,里面的环境变量设置会替换已有的设置,但是不影响外层的执行环境,只影响当前命令。
我们也可以通过 os.getenvs()
接口获取当前所有的环境变量,然后改写部分后传入 envs 参数。
os.iorun
安静运行原生 shell 命令并获取输出内容。
与 os.run 接口类似,唯一的不同是,此接口执行 shell 程序后,会获取 shell 程序的执行结果,相当于重定向输出。
可同时获取 stdout
, stderr
中的内容,例如:
local outdata, errdata = os.iorun("echo hello xmake!")
我们也可以利用 bash -c
来实现管道命令:
local output = os.iorun([[bash -c "cat /etc/passwd | grep '/bin/bash' | wc -l | tr -d '\n'"]])
print("match lines count: [" .. output .. "]")
os.iorunv
安静运行原生 shell 命令并获取输出内容,带参数列表。
跟 os.iorun 类似,只是传递参数的方式是通过参数列表传递,而不是字符串命令,例如:
local outdata, errdata = os.iorunv("echo", {"hello", "xmake!"})
另外,此接口也支持 envs 参数设置:
local outdata, errdata = os.iorunv("echo", {"hello", "xmake!"}, {envs = {PATH = "xxx;xx", CFLAGS = "xx"}}
os.iorun_with_pipes
安静运行原生 shell 命令并获取输出内容,支持管道。
-- 可以运行带管道符的复杂 shell 命令
-- @see https://github.com/TOMO-CAT/xmake/issues/140
--
-- 官方实现方法比较复杂: https://github.com/xmake-io/xmake/discussions/6002
local output = os.iorun_with_pipes(
"cat /etc/passwd | grep '/bin/bash' | wc -l | tr -d '\n'")
print("match lines count: [" .. output .. "]")
os.getenv
获取系统环境变量。
print(os.getenv("PATH"))
os.setenv
设置系统环境变量。
os.setenv("HOME", "/tmp/")
os.tmpdir
获取临时目录。
跟 $(tmpdir) 结果一致,只不过是直接获取返回一个变量,可以用后续字符串维护。
print(path.join(os.tmpdir(), "file.txt"))
等价于:
print("$(tmpdir)/file.txt")
os.tmpfile
获取临时文件路径。
用于获取生成一个临时文件路径,仅仅是个路径,文件需要自己创建。
os.curdir
获取当前目录路径。
跟 $(curdir) 结果一致,只不过是直接获取返回一个变量,可以用后续字符串维护。
用法参考:os.tmpdir。
os.filesize
获取文件大小。
print(os.filesize("/tmp/a"))
os.scriptdir
获取当前描述脚本的路径。
跟 $(scriptdir) 结果一致,只不过是直接获取返回一个变量,可以用后续字符串维护。
用法参考:os.tmpdir。
os.programdir
获取 xmake 安装主程序脚本目录。
跟 $(programdir) 结果一致,只不过是直接获取返回一个变量,可以用后续字符串维护。
os.programfile
获取 xmake 可执行文件路径。
os.projectdir
获取工程主目录。
跟 $(projectdir) 结果一致,只不过是直接获取返回一个变量,可以用后续字符串维护。
os.arch
获取当前系统架构。
也就是当前主机系统的默认架构,例如我在 linux x86_64
上执行 xmake 进行构建,那么返回值是:x86_64
os.host
获取当前主机的操作系统。
跟 $(host) 结果一致,例如我在 linux x86_64
上执行 xmake 进行构建,那么返回值是:linux
os.subhost
获取当前子系统,如:在 Windows 上的 msys、cygwin。
os.subarch
获取子系统架构。
os.is_host
判断给定系统是否为当前系统。
os.is_arch
判断给定架构是否为当前架构。
os.is_subhost
判断给定子系统是否为当前子系统。
os.is_subarch
判断给定子系统架构是否为当前子系统架构。
os.ln
为一个文件或目录创建符号链接。
-- 创建一个指向 "tmp.txt" 文件的符号链接 "tmp.txt.ln"
os.ln("xxx.txt", "xxx.txt.ln")
os.readlink
读取符号链接内容。
os.raise
抛出一个异常并且中止当前脚本运行。
-- 抛出一个带 "an error occurred" 信息的异常
os.raise("an error occurred")
推荐使用与
os.raise
等价的内置接口raise
,用法与os.raise
一致
os.raiselevel
与 os.raise 类似但是可以指定异常等级。
-- 抛出一个带 "an error occurred" 信息的异常
os.raise(3, "an error occurred")
os.features
获取系统特性。
os.getenvs
获取所有当前系统变量。
local envs = os.getenvs()
-- home directory (on linux)
print(envs["HOME"])
os.setenvs
使用给定系统变量替换当前所有系统变量,并返回旧系统变量。
os.addenvs
向当前系统变量添加新变量,并且返回所有旧系统变量。
os.setenvs({EXAMPLE = "a/path"}) -- add a custom variable to see addenvs impact on it
local oldenvs = os.addenvs({EXAMPLE = "some/path/"})
print(os.getenvs()["EXAMPLE"]) --got some/path/;a/path
print(oldenvs["EXAMPLE"]) -- got a/path
os.joinenvs
拼接系统变量,与 os.addenvs 类似,但是不会对当前环境变量产生影响,若第二个参数为 nil
,则使用原有环境变量。
-- os.joinenvs(envs, oldenvs)
--
-- @param envs table 类型,新插入的环境变量
--
-- @param oldenvs table 类型,被插入的环境变量,若为 nil, 则为原有环境变量
--
-- @return table 类型,拼接后的环境变量
local envs0 = {CUSTOM = "a/path"}
local envs1 = {CUSTOM = "some/path/"}
print(os.joinenvs(envs0, envs1)) -- result is : { CUSTION = "a/path;some/path/" }
os.setenvp
使用给定分隔符设置环境变量。
os.workingdir
获取工作目录。
os.isroot
判断 xmake 是否以管理员权限运行。
os.fscase
判断操作系统的文件系统是否大小写敏感。
os.term
获取当前终端 (windows-terminal, vscode, xterm, ...)。
os.shell
获取当前 shell (pwsh, cmd, bash, zsh, ...)。
os.cpuinfo
获取当前 CPU 信息。
print(os.cpuinfo())
-- probably got {
-- march = "Alder Lake",
-- model = 154,
-- ncpu = 20,
-- model_name = "12th Gen Intel(R) Core(TM) i9-12900H",
-- usagerate = 0.041839182376862,
-- vendor = "GenuineIntel",
-- family = 6
-- }
print(os.cpuinfo("march")) -- probably got "Alder Lake"
os.meminfo
获取内存信息。
print(os.meminfo())
-- probably got {
-- pagesize = 4096,
-- usagerate = 0.60694103194103,
-- availsize = 12798,
-- totalsize = 32560
-- }
print(os.meminfo("pagesize")) -- probably got 4096
os.default_njob
获取默认编译任务数。
macos
macOS 系统操作模块,属于内置模块,无需使用 import 导入,可直接脚本域调用其接口。
接口 | 描述 | 支持版本 |
---|---|---|
macos.version | 获取 macOS 系统版本 | >= 2.3.1 |
macos.version
获取 macOS 系统版本。
返回的版本是 semver 语义版本对象
if macos.version():ge("10.0") then
-- ...
end
linuxos
linux 系统操作模块,属于内置模块,无需使用 import 导入,可直接脚本域调用其接口。
接口 | 描述 | 支持版本 |
---|---|---|
linuxos.name | 获取 linux 系统发行版名称 | >= 2.5.2 |
linuxos.version | 获取 linux 系统版本 | >= 2.5.2 |
linuxos.kernelver | 获取 linux 系统内核版本 | >= 2.5.2 |
linuxos.name
获取 linux 系统发行版名称。
我们也可以通过下面的命令,快速获取查看
xmake l linuxos.name
目前支持的一些名称有:
- ubuntu
- debian
- archlinux
- manjaro
- linuxmint
- centos
- fedora
- opensuse
linuxos.version
获取 linux 系统版本。
返回的版本是 semver 语义版本对象
if linux.version():ge("10.0") then
-- ...
end
linuxos.kernelver
获取 linux 系统内核版本。
返回的也是语义版本对象,也可以执行 xmake l linuxos.kernelver
快速查看
io
io 操作模块,扩展了 lua 内置的 io 模块,提供更多易用的接口。
接口 | 描述 | 支持版本 |
---|---|---|
io.open | 打开文件用于读写 | >= 2.0.1 |
io.load | 从指定路径文件反序列化加载所有 table 内容 | >= 2.0.1 |
io.save | 序列化保存所有 table 内容到指定路径文件 | >= 2.0.1 |
io.readfile | 从指定路径文件读取所有内容 | >= 2.1.3 |
io.writefile | 写入所有内容到指定路径文件 | >= 2.1.3 |
io.gsub | 全文替换指定路径文件的内容 | >= 2.0.1 |
io.tail | 读取和显示文件的尾部内容 | >= 2.0.1 |
io.cat | 读取和显示文件的所有内容 | >= 2.0.1 |
io.print | 带换行格式化输出内容到文件 | >= 2.0.1 |
io.printf | 无换行格式化输出内容到文件 | >= 2.0.1 |
io.lines | 读取文件的所有行 | >= 2.2.9 |
io.stdfile | 获取标准输入输出文件 | >= 2.2.9 |
io.openlock | 创建一把文件锁 | >= 2.2.9 |
io.replace | 根据表达式替换文件内容 | >= 2.3.8 |
io.open
打开文件用于读写。
这个是属于 lua 的原生接口,详细使用可以参看 lua 的官方文档:The Complete I/O Model
如果要读取文件所有内容,可以这么写:
local file = io.open("$(tmpdir)/file.txt", "r")
if file then
local data = file:read("*all")
file:close()
end
或者可以使用 io.readfile 更加快速地读取。
如果要写文件,可以这么操作:
-- 打开文件:w 为写模式, a 为追加写模式
local file = io.open("xxx.txt", "w")
if file then
-- 用原生的 lua 接口写入数据到文件,不支持格式化,无换行,不支持内置变量
file:write("hello xmake\n")
-- 用 xmake 扩展的接口写入数据到文件,支持格式化,无换行,不支持内置变量
file:writef("hello %s\n", "xmake")
-- 使用 xmake 扩展的格式化传参写入一行,带换行符,并且支持内置变量
file:print("hello %s and $(buildir)", "xmake")
-- 使用 xmake 扩展的格式化传参写入一行,无换行符,并且支持内置变量
file:printf("hello %s and $(buildir) \n", "xmake")
-- 关闭文件
file:close()
end
io.load
从指定路径文件反序列化加载所有 table 内容。
可以从文件中加载序列化好的 table 内容,一般与 io.save 配合使用,例如:
-- 加载序列化文件的内容到 table
local data = io.load("xxx.txt")
if data then
-- 在终端中 dump 打印整个 table 中内容,格式化输出
utils.dump(data)
end
io.save
序列化保存所有 table 内容到指定路径文件。
可以序列化存储 table 内容到指定文件,一般与 io.load 配合使用,例如:
io.save("xxx.txt", {a = "a", b = "b", c = "c"})
存储结果为:
{
["b"] = "b"
, ["a"] = "a"
, ["c"] = "c"
}
io.readfile
从指定路径文件读取所有内容。
可在不打开文件的情况下,直接读取整个文件的内容,更加的方便,例如:
local data = io.readfile("xxx.txt")
io.writefile
写入所有内容到指定路径文件。
可在不打开文件的情况下,直接写入整个文件的内容,更加的方便,例如:
io.writefile("xxx.txt", "all data")
io.gsub
全文替换指定路径文件的内容。
类似 string.gsub 接口,全文模式匹配替换内容,不过这里是直接操作文件,例如:
-- 移除文件所有的空白字符
io.gsub("xxx.txt", "%s+", "")
io.tail
读取和显示文件的尾部内容。
读取文件尾部指定行数的数据,并显示,类似 cat xxx.txt | tail -n 10
命令,例如:
-- 显示文件最后 10 行内容
io.tail("xxx.txt", 10)
io.cat
读取和显示文件的所有内容。
读取文件的所有内容并显示,类似 cat xxx.txt
命令,例如:
io.cat("xxx.txt")
io.print
带换行格式化输出内容到文件。
直接格式化传参输出一行字符串到文件,并且带换行,例如:
io.print("xxx.txt", "hello %s!", "xmake")
io.printf
无换行格式化输出内容到文件。
直接格式化传参输出一行字符串到文件,不带换行,例如:
io.printf("xxx.txt", "hello %s!\n", "xmake")
io.lines
读取文件的所有行。
根据文件名返回该文件的所有行的内容
local lines = io.lines("xxx.txt")
for line in lines do
print(line)
end
io.stdfile
获取标准输入输出文件。
根据文件名返回标准输入输出文件
-- 标准输入
io.stdin
-- 标准输出
io.stdout
-- 标准错误
io.stderr
io.openlock
创建一把文件锁。
为给定的文件返回一个文件锁对象
local lock = io.openlock("xxx.txt")
lock:lock()
lock:unlock()
lock:close()
io.replace
根据表达式替换文件内容。
根据表达式和参数对文件进行全文替换
io.replace(filepath, pattern, replace, opt)
io.replace("xxx.txt", "test", "xmake", { plain = true, encoding = "UTF-8"})
io.replace("xxx.txt", "%d[^\n]*", "xmake")
关于参数 opt
成员的解释:
.plain: 若为 true,使用 pattern 进行简单匹配;为 false,则进行模式匹配;
.encoding: 指定文件编码格式
path
路径操作模块,实现跨平台的路径操作,这是 xmake 的一个自定义的模块。
接口 | 描述 | 支持版本 |
---|---|---|
path.join | 拼接路径 | >= 2.0.1 |
path.translate | 转换路径到当前平台的路径风格 | >= 2.0.1 |
path.basename | 获取路径最后不带后缀的文件名 | >= 2.0.1 |
path.filename | 获取路径最后带后缀的文件名 | >= 2.0.1 |
path.extension | 获取路径的后缀名 | >= 2.0.1 |
path.directory | 获取路径的目录名 | >= 2.0.1 |
path.relative | 转换成相对路径 | >= 2.0.1 |
path.absolute | 转换成绝对路径 | >= 2.0.1 |
path.is_absolute | 判断是否为绝对路径 | >= 2.0.1 |
path.splitenv | 分割环境变量中的路径 | >= 2.2.7 |
path.join
拼接路径。
将多个路径项进行追加拼接,由于 windows/unix
风格的路径差异,使用 api 来追加路径更加跨平台,例如:
print(path.join("$(tmpdir)", "dir1", "dir2", "file.txt"))
上述拼接在 unix 上相当于:$(tmpdir)/dir1/dir2/file.txt
,而在 windows 上相当于:$(tmpdir)\\dir1\\dir2\\file.txt
如果觉得这样很繁琐,不够清晰简洁,可以使用:path.translate 方式,格式化转换路径字符串到当前平台支持的格式。
path.translate
转换路径到当前平台的路径风格。
格式化转化指定路径字符串到当前平台支持的路径风格,同时支持 windows/unix
格式的路径字符串参数传入,甚至混合传入,例如:
print(path.translate("$(tmpdir)/dir/file.txt"))
print(path.translate("$(tmpdir)\\dir\\file.txt"))
print(path.translate("$(tmpdir)\\dir/dir2//file.txt"))
上面这三种不同格式的路径字符串,经过 translate
规范化后,就会变成当前平台支持的格式,并且会去掉冗余的路径分隔符。
path.basename
获取路径最后不带后缀的文件名。
print(path.basename("$(tmpdir)/dir/file.txt"))
显示结果为:file
path.filename
获取路径最后带后缀的文件名。
print(path.filename("$(tmpdir)/dir/file.txt"))
显示结果为:file.txt
path.extension
获取路径的后缀名。
print(path.extensione("$(tmpdir)/dir/file.txt"))
显示结果为:.txt
path.directory
获取路径的目录名。
print(path.directory("$(tmpdir)/dir/file.txt"))
显示结果为:$(tmpdir)/dir
path.relative
转换成相对路径。
print(path.relative("$(tmpdir)/dir/file.txt", "$(tmpdir)"))
显示结果为:dir/file.txt
第二个参数是指定相对的根目录,如果不指定,则默认相对当前目录:
os.cd("$(tmpdir)")
print(path.relative("$(tmpdir)/dir/file.txt"))
这样结果是一样的。
path.absolute
转换成绝对路径。
print(path.absolute("dir/file.txt", "$(tmpdir)"))
显示结果为:$(tmpdir)/dir/file.txt
第二个参数是指定相对的根目录,如果不指定,则默认相对当前目录:
os.cd("$(tmpdir)")
print(path.absolute("dir/file.txt"))
这样结果是一样的。
path.is_absolute
判断是否为绝对路径。
if path.is_absolute("/tmp/file.txt") then
-- 如果是绝对路径
end
path.splitenv
分割环境变量中的路径。
local pathes = path.splitenv(vformat("$(env PATH)"))
-- for windows
local pathes = path.splitenv("C:\\Windows;C:\\Windows\\System32")
-- got {"C:\\Windows", "C:\\Windows\\System32"}
-- for *nix
local pathes = path.splitenv("/usr/bin:/usr/local/bin")
-- got {"/usr/bin", "/usr/local/bin"}
结果为一个包含了输入字符串中路径的数组。
table
table 属于 lua 原生提供的模块,对于原生接口使用可以参考:lua 官方文档
xmake 中对其进行了扩展,增加了一些扩展接口:
接口 | 描述 | 支持版本 |
---|---|---|
table.join | 合并多个 table 并返回 | >= 2.0.1 |
table.join2 | 合并多个 table 到第一个 table | >= 2.0.1 |
table.unique | 对 table 中的内容进行去重 | >= 2.0.1 |
table.slice | 获取 table 的切片 | >= 2.0.1 |
table.join
合并多个 table 并返回。
可以将多个 table 里面的元素进行合并后,返回到一个新的 table 中,例如:
local newtable = table.join({1, 2, 3}, {4, 5, 6}, {7, 8, 9})
结果为:{1, 2, 3, 4, 5, 6, 7, 8, 9}
并且它也支持字典的合并:
local newtable = table.join({a = "a", b = "b"}, {c = "c"}, {d = "d"})
结果为:{a = "a", b = "b", c = "c", d = "d"}
table.join2
合并多个 table 到第一个 table。
类似 table.join,唯一的区别是,合并的结果放置在第一个参数中,例如:
local t = {0, 9}
table.join2(t, {1, 2, 3})
结果为:t = {0, 9, 1, 2, 3}
table.unique
对 table 中的内容进行去重。
去重 table 的元素,一般用于数组 table,例如:
local newtable = table.unique({1, 1, 2, 3, 4, 4, 5})
结果为:{1, 2, 3, 4, 5}
table.slice
获取 table 的切片。
用于提取数组 table 的部分元素,例如:
-- 提取第 4 个元素后面的所有元素,结果:{4, 5, 6, 7, 8, 9}
table.slice({1, 2, 3, 4, 5, 6, 7, 8, 9}, 4)
-- 提取第 4-8 个元素,结果:{4, 5, 6, 7, 8}
table.slice({1, 2, 3, 4, 5, 6, 7, 8, 9}, 4, 8)
-- 提取第 4-8 个元素,间隔步长为 2,结果:{4, 6, 8}
table.slice({1, 2, 3, 4, 5, 6, 7, 8, 9}, 4, 8, 2)
table.contains
判断 table 中包含指定的值。
if table.contains(t, 1, 2, 3) then
-- ...
end
只要 table 中包含 1, 2, 3 里面任意一个值,则返回 true
table.orderkeys
获取有序的 key 列表。
table.keys(t)
返回的 key 列表顺序是随机的,想要获取有序 key 列表,可以用这个接口。
string
字符串模块为 lua 原生自带的模块,具体使用见:lua 官方手册
xmake 中对其进行了扩展,增加了一些扩展接口:
接口 | 描述 | 支持版本 |
---|---|---|
string.startswith | 判断字符串开头是否匹配 | >= 1.0.1 |
string.endswith | 判断字符串结尾是否匹配 | >= 1.0.1 |
string.split | 分割字符串 | >= 1.0.1 |
string.trim | 去掉字符串左右空白字符 | >= 1.0.1 |
string.ltrim | 去掉字符串左边空白字符 | >= 1.0.1 |
string.rtrim | 去掉字符串右边空白字符 | >= 1.0.1 |
string.startswith
判断字符串开头是否匹配。
local s = "hello xmake"
if s:startswith("hello") then
print("match")
end
string.endswith
判断字符串结尾是否匹配。
local s = "hello xmake"
if s:endswith("xmake") then
print("match")
end
string.split
分割字符串。
按模式匹配分割字符串,忽略空串,例如:
("1\n\n2\n3"):split('\n') => 1, 2, 3
("abc123123xyz123abc"):split('123') => abc, xyz, abc
("abc123123xyz123abc"):split('[123]+') => abc, xyz, abc
按纯文本匹配分割字符串,忽略空串(省去了模式匹配,会提升稍许性能),例如:
("1\n\n2\n3"):split('\n', {plain = true}) => 1, 2, 3
("abc123123xyz123abc"):split('123', {plain = true}) => abc, xyz, abc
按模式匹配分割字符串,严格匹配,不忽略空串,例如:
("1\n\n2\n3"):split('\n', {strict = true}) => 1, , 2, 3
("abc123123xyz123abc"):split('123', {strict = true}) => abc, , xyz, abc
("abc123123xyz123abc"):split('[123]+', {strict = true}) => abc, xyz, abc
按纯文本匹配分割字符串,严格匹配,不忽略空串(省去了模式匹配,会提升稍许性能),例如:
("1\n\n2\n3"):split('\n', {plain = true, strict = true}) => 1, , 2, 3
("abc123123xyz123abc"):split('123', {plain = true, strict = true}) => abc, , xyz, abc
限制分割块数
("1\n\n2\n3"):split('\n', {limit = 2}) => 1, 2\n3
("1.2.3.4.5"):split('%.', {limit = 3}) => 1, 2, 3.4.5
string.trim
去掉字符串左右空白字符。
string.trim(" hello xmake! ")
结果为:"hello xmake!"
string.ltrim
去掉字符串左边空白字符。
string.ltrim(" hello xmake!")
结果为:"hello xmake!"
string.rtrim
去掉字符串右边空白字符。
string.rtrim("hello xmake! ")
结果为:"hello xmake!"
coroutine
协程模块是 lua 原生自带的模块,具使用见:lua 官方手册
signal
我们可以在 lua 层,注册 SIGINT 等信号处理函数,来定制化响应逻辑。
signal.register
注册信号处理器。
目前仅仅支持 SIGINT 信号的处理,同时它也是支持 windows 等主流平台的。
import("core.base.signal")
function main()
signal.register(signal.SIGINT, function (signo)
print("signal.SIGINT(%d)", signo)
end)
io.read()
end
这对于当一些子进程内部屏蔽了 SIGINT,导致卡死不退出,即使用户按了 Ctrl+C
退出了 xmake 进程,它也没有退出时候,
我们就可以通过这种方式去强制退掉它。
import("core.base.process")
import("core.base.signal")
function main()
local proc
signal.register(signal.SIGINT, function (signo)
print("sigint")
if proc then
proc:kill()
end
end)
proc = process.open("./trap.sh")
if proc then
proc:wait()
proc:close()
end
end
关于这个问题的背景,可以参考:#4889
signal.ignore
忽略某个信号。
我们也可以通过 signal.ignore
这个接口,去忽略屏蔽某个信号的处理。
signal.ignore(signal.SIGINT)
signal.reset
重置某个信号。
我们也可以清除某个信号的处理函数,回退到默认的处理逻辑。
signal.reset(signal.SIGINT)