back

使用 Xmake 作为 CS149 作业的 bulild system

前言

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()
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()