前言
CS149 是一个教并行编程的课程,课程作业使用了 makefile 作为 build system。
本文通过改造 build system 来讲述一些xmake
的使用方法。
分析
这个课程主要有四个作业,那么是一个多target
工程,考虑用多级配置。
作业里用到了很多工具,xmake
都支持这些工具链。不过有些作业不跨平台(只能在 Linux 下运行),所以尽可能地将作业移植到 windows 上。
改造
完整配置在GitHub上,这里选讲几个部分。
根目录配置
- 最小
xmake
版本。
set_xmakever("2.7.5")
- 跨平台首选
clang
作为 C++ 编译器,在 windows 下使用clang-cl
接受 msvc 风格的参数。 - 在开发环境下,链接动态库可以减少二进制体积,加速链接。
- 添加宏定义处理一些 windows 的屎。
if is_plat("windows") then
-- set_toolchains("clang-cl")
set_runtimes("MD")
add_defines("_CRT_SECURE_NO_WARNINGS", "NOMINMAX")
else
-- set_toolchains("clang")
end
源码目录配置
- 考虑到 windows 没有
getopt.h
,这里在 github 上随便找了一个库代替,并开放头文件权限给依赖此库的target
使用。
if is_plat("windows") then
target("getopt")
set_kind("static")
add_includedirs("getopt-for-windows", {public = true})
add_files("getopt-for-windows/getopt.c")
target_end()
end
作业1
作业有多个target
,其实可以考虑每个给每个target
单独一个配置,不过这里选择把多个target
都放在同一个xmake.lua
中。
- 根据原始 makefile 参数,处理一些跨平台配置。
- 考虑到作业会生成 ppm 格式的图片,所以改变该
target
的运行目录。
target("mandelbrot")
set_kind("binary")
add_files("prog1_mandelbrot_threads/*.cpp")
set_optimize("fastest")
add_deps("common")
if is_plat("windows") then
add_deps("getopt")
elseif is_plat("linux") then
add_syslinks("m", "pthread")
end
set_rundir("prog1_mandelbrot_threads")
target_end()
- 这里用到了
ispc
进行编译,使用xmake
的内置规则进行编译。 - 编译 ispc 文件会生成一个对象文件和头文件,这里通过
header_extension
配置控制生成头文件的后缀,然后就可以在代码中引用生成的头文件xxx_ispc.h
(xxx.ispc)。 - 由于没有内置的 ispc api,这里使用
set_values
传递编译参数。
target("sqrt")
set_kind("binary")
add_rules("utils.ispc", {header_extension = "_ispc.h"})
add_files("prog4_sqrt/*.ispc")
add_files("prog4_sqrt/*.cpp")
add_cxxflags("-march=native")
set_values("ispc.flags", "--target=avx2-i32x8", "--arch=x86-64")
set_optimize("fastest")
add_deps("common")
if is_plat("linux") then
add_syslinks("m", "pthread")
end
target_end()
作业2
本作业两个target
的配置相同,可以用一个循环来生成。(常用于生成相同配置的单元测试)
for _, part in ipairs({"part_a", "part_b"}) do
target(part)
set_kind("binary")
add_includedirs(part)
add_includedirs("../common", "tests")
add_files(part .. "/*.cpp")
add_files("tests/main.cpp")
if is_plat("windows") then
add_deps("getopt")
elseif is_plat("linux") then
add_syslinks("m", "pthread")
end
target_end()
end
- 作业内置 test,但官方没有提供完成所有测试的脚本(其实直接改源码也可以),不过这里考虑用 xmake 的脚本域来做这件事。
- 设置一个空目标类型,覆盖内置的on_run,然后使用 xmake 一些内置的 api 运行测试。
target("part_a_test")
set_kind("phony")
add_deps("part_a")
on_run(function ()
local test_names =
{
"simple_test_sync",
"ping_pong_equal",
"ping_pong_unequal",
"super_light",
"super_super_light",
"recursive_fibonacci",
"math_operations_in_tight_for_loop",
"math_operations_in_tight_for_loop_fewer_tasks",
"math_operations_in_tight_for_loop_fan_in",
"math_operations_in_tight_for_loop_reduction_tree",
"spin_between_run_calls",
"mandelbrot_chunked",
}
for _, value in ipairs(test_names)
do
-- cprint("Testing " .. "${bright green}%s", value)
-- os.runv(name, {value})
os.execv("xmake", {"run", "part_a", value})
end
cprint("${bright red}Pass!")
end)
target_end()
作业3
xmake 会自动探测 cuda 的目录,如果不在默认的安装目录,设置一下。
xmake f --cuda=<PATH>
- xmake 对
cuda
支持还算完善,直接add_files
就能直接编译。 - cuda runtime 也有动态库,不过 xmake 默认链接静态库,这里需要手动添加链接动态库的名字。
add_cugencodes("compute_35")
add_links("cudart")
target("render")
set_kind("binary")
add_files("render/*.cpp")
add_files("render/*.cu")
add_packages("freeglut")
set_rundir("render")
target_end()
作业4
该作业只能运行在 Linux 平台,因为官方只提供了 Linux 平台的二进制进行链接。
- 这里写了一个函数禁用
target
,然后在on_load时禁用,可以使用其他写法禁用target
。 - 官方提供了一个静态库,但静态库名字前缀不是
lib
开头,链接器不接受,所以不能直接用add_links
function only_linux(target)
if target:is_plat("linux") then
target:set("enabled", true)
else
target:set("enabled", false)
end
end
target("pr")
set_kind("binary")
add_files("pagerank/main.cpp", "pagerank/page_rank.cpp")
add_linkdirs("pagerank")
add_ldflags("-fopenmp")
add_ldflags("-l:ref_pr.a", {force = true})
add_deps("assignment4_common")
on_load(only_linux)
target_end()
- 对象文件也可也直接add_files
target("bfs")
set_kind("binary")
add_files("bfs/main.cpp", "bfs/bfs.cpp")
add_files("bfs/ref_bfs.o")
add_ldflags("-fopenmp")
add_deps("assignment4_common")
on_load(only_linux)
target_end()