V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
waruqi
V2EX  ›  编程

xmake 新版本即将发布,带来大量新特性,欢迎大家试用哦

  •  1
     
  •   waruqi · 2017-07-31 14:47:36 +08:00 · 2932 次点击
    这是一个创建于 2672 天前的主题,其中的信息可能已经有所发展或是发生改变。

    新版本现已进入收尾阶段,此版本加入了一大波新特性,欢迎大家过来试用哦,目前正在进行稳定性测试和修复.

    在这里,先来介绍下新版本中引入了哪些新特性和改进:

    1. 提供类似 cmake 的 find_*系列接口,实现各种查找,例如:find_package, find_library, find_file, ...
    2. 提供模块接口,实现编译器的各种检测,例如:has_features, has_flags, has_cincludes, has_cfuncs, ...
    3. 实现大量扩展模块,提供文件下载、解压缩、git 操作等接口
    4. 支持预编译头文件支持,改进 c++编译效率
    5. 支持在工程中自定义模块进行扩展
    6. 提供代码片段检测接口,实现更加灵活定制化的检测需求
    7. 改进 option 和 target,提供更加动态化的配置
    8. 通过 find_package 实现包依赖管理 2.0 版本
    9. 改进 root 权限问题,实现更加安全的 root 下运行
    10. 提供 compile_commands.json 导出插件
    11. 改进 vs201x 工程生成插件,支持多模式、多架构同时构建和自由切换不干扰
    

    利用 find_package 查找依赖包

    此接口参考了 cmake 对于find_*系列接口的设计,实现在项目中动态的查找和添加包依赖。

    target("test")
        set_kind("binary")
        add_files("*.c")
        on_load(function (target)
            import("lib.detect.find_package")
            target:add(find_package("zlib"))
        end)
    

    上述描述代码,通过lib.detect.find_package来查找包,如果找到zlib包,则将links, includedirslinkdirs等信息添加到 target 中去。

    实现包管理 2.0

    2.1.4 版本之前,xmake 对于包管理,是通过在项目内置pkg/zlib.pkg方式,来检测链接的,虽然也支持自动检测,但是查找功能有限,并且内置的各个架构的二进制库到项目,对 git 并不是很友好。

    现在通过find_packageoption,我们可以实现更好的包管理:

    option("zlib")
        set_showmenu(true)
        before_check(function (option)
            import("lib.detect.find_package")
            option:add(find_package("zlib"))
        end)
    
    target("test")
        add_options("zlib")
    

    通过定义一个名为 zlib 的选项作为包,关联到 target,在选项被检测之前,先从系统中查找 zlib 包,如果存在,则添加对应的links, linkdirs等配置信息,然后进行选项检测,如果选项检测通过,这个 target 在编译的时候就会启用 zlib。

    如果要手动禁用这个 zlib 包,使其不参与自动检测和链接,只需要:

    $ xmake f --zlib=n 
    $ xmake
    

    注:2.2.1 版本将会实现包管理 3.0,更加自动化的依赖包管理和使用,具体详情见:Remote package management

    例如:

    add_requires("mbedtls master optional")
    add_requires("pcre2 >=1.2.0", "zlib >= 1.2.11")
    add_requires("[email protected]:glennrp/libpng.git@libpng >=1.6.28")
    target("test")
        add_packages("pcre2", "zlib", "libpng", "mbedtls")
    

    目前正在努力开发中,尽情期待。。

    模块的自定义扩展

    我们可以通过在工程的xmake.lua文件的开头指定下扩展 modules 的目录:

    add_moduledirs("$(projectdir)/xmake/modules")
    

    这样 xmake 就能找到自定义的扩展模块了,例如:

    projectdir
     - xmake
       - modules
         - detect/package/find_openssl.lua
    

    通过在自定义的工程模块目录,添加一个find_openssl.lua的脚本,就可以扩展find_package,使得包查找更加精准。

    这里顺便总结下,find_package的查找顺序:

    1. 如果指定{packagedirs = ""}参数,优先从这个参数指定的路径中查找本地包*.pkg
    2. 如果在xmake/modules下面存在detect.packages.find_xxx脚本,那么尝试调用此脚本来改进查找结果
    3. 如果系统存在pkg-config,并且查找的是系统环境的库,则尝试使用pkg-config提供的路径和链接信息进行查找
    4. 如果系统存在homebrew,并且查找的是系统环境的库,则尝试使用brew --prefix xxx提供的信息进行查找
    5. 从参数中指定的 pathes 路径和一些已知的系统路径/usr/lib, /usr/include中进行查找

    快速判断编译器特性检测支持

    通过core.tool.compiler模块的compiler.has_features接口,在xmake.lua中预先判断当前编译期支持的语言特性,实现条件编译。

    此处也是参考了 cmake 的设计,具体详情见:issues#83

    target("test")
        on_load(function (target)
            import("core.tool.compiler")
            if compiler.has_features("cxx_constexpr") then
                target:add("defines", "HAS_CXX_CONSTEXPR=1")
            end
        end)
    

    上述代码,在加载 target 的时候,判断当前编译器是否支持 c++的常量表达式语法特性,如果支持则添加宏定义:HAS_CXX_CONSTEXPR=1

    我们也可以在判断时候,追加一些参数控制编译选项,例如上述特性需要c++11支持,我们可以启用它:

    if compiler.has_features({"c_static_assert", "cxx_constexpr"}, {languages = "cxx11"}) then
        -- ok
    end
    

    如果之前对这个 target 已经设置了c++11,那么我们也可以传入 target 对象,继承 target 的所有设置:

    if compiler.has_features("cxx_constexpr", {target = target, defines = "..", includedirs = ".."}) then
        -- ok
    end
    

    所有的 c/c++编译器特性列表,见:compiler.features

    判断指定 c/c++头文件是否存在

    通过lib.detect.has_cincludes来检测 c 头文件是否存在。

    import("lib.detect.has_cincludes")
    
    local ok = has_cincludes("stdio.h")
    local ok = has_cincludes({"stdio.h", "stdlib.h"}, {target = target})
    local ok = has_cincludes({"stdio.h", "stdlib.h"}, {defines = "_GNU_SOURCE=1", languages = "cxx11"})
    

    c++头文件的检测,见:lib.detect.has_cxxincludes

    判断指定 c/c++函数是否存在

    通过lib.detect.has_cfuncs来检测 c 函数是否存在。

    import("lib.detect.has_cfuncs")
    
    local ok = has_cfuncs("setjmp")
    local ok = has_cfuncs({"sigsetjmp((void*)0, 0)", "setjmp"}, {includes = "setjmp.h"})
    

    c++函数的检测,见:lib.detect.has_cxxfuncs

    判断指定 c/c++类型是否存在

    通过lib.detect.has_ctypes来检测 c 函数是否存在。

    import("lib.detect.has_ctypes")
    
    local ok = has_ctypes("wchar_t")
    local ok = has_ctypes({"char", "wchar_t"}, {includes = "stdio.h"})
    local ok = has_ctypes("wchar_t", {includes = {"stdio.h", "stdlib.h"}, "defines = "_GNU_SOURCE=1", languages = "cxx11"})
    

    c++类型的检测,见:lib.detect.has_cxxtypes

    检测 c/c++代码片段是否能够编译通过

    通用的 c/c++代码片段检测接口,通过传入多个代码片段列表,它会自动生成一个编译文件,然后常识对它进行编译,如果编译通过返回 true。

    对于一些复杂的编译器特性,连compiler.has_features都无法检测到的时候,可以通过此接口通过尝试编译来检测它。

    import("lib.detect.check_cxsnippets")
    
    local ok = check_cxsnippets("void test() {}")
    local ok = check_cxsnippets({"void test(){}", "#define TEST 1"}, {types = "wchar_t", includes = "stdio.h"})
    

    此接口是detect.has_cfuncs, detect.has_cincludesdetect.has_ctypes等接口的通用版本,也更加底层。

    因此我们可以用它来检测:types, functions, includes 还有 links,或者是组合起来一起检测。

    第一个参数为代码片段列表,一般用于一些自定义特性的检测,如果为空,则可以仅仅检测可选参数中条件,例如:

    local ok = check_cxsnippets({}, {types = {"wchar_t", "char*"}, includes = "stdio.h", funcs = {"sigsetjmp", "sigsetjmp((void*)0, 0)"}})
    

    上面那个调用,会去同时检测 types, includes 和 funcs 是否都满足,如果通过返回 true。

    更加强大的 xmake lua 插件

    2.1.4 版本的时候,此插件就已经支持 REPL(read-eval-print),实现交互式运行来方便测试模块:

    $ xmake lua
    > 1 + 2
    3
    
    > a = 1
    > a
    1
    
    > for _, v in pairs({1, 2, 3}) do
    >> print(v)
    >> end
    1
    2
    3
    

    现在可以通过一行命令,更加快速地测试模块接口:

    $ xmake lua lib.detect.find_package openssl
    

    返回结果如下:{links = {"ssl", "crypto", "z"}, linkdirs = {"/usr/local/lib"}, includedirs = {"/usr/local/include"}}

    预编译头文件支持

    xmake 新增通过预编译头文件去加速c/c++程序编译,目前支持的编译器有:gcc, clang 和 msvc。

    使用方式如下:

    target("test")
        set_precompiled_header("header.h")
    

    通常情况下,设置 c 头文件的预编译,这需要加上这个配置即可,如果是对 c++头文件的预编译,改成:

    target("test")
        set_precompiled_header("header.hpp")
    

    其中的参数指定的是需要预编译的头文件路径,相对于当前xmake.lua所在的目录。

    如果只是调用 xmake 命令行进行直接编译,那么上面的设置足够了,并且已经对各个编译器进行支持,但是有些情况下,上面的设置还不能满足需求:

    1. 如果要使用xmake project工程插件生成 vs 工程文件,那么还缺少一个类似stdafx.cpp的文件(上面的设置在 msvc 编译的时候会自动生成一个临时的,但是对 IDE 工程不友好)。
    2. 如果 gcc/clang 下,header.h想作为 c++的预编译头文件就不支持了,除非改成header.hpp(默认会当做 c 头文件进行预编译)。

    因此为了更加地通用跨平台,可以在工程里面创建一个类似 vc 中stdafx.cpp的源文件:header.cpp

    target("test")
        set_precompiled_header("header.h", "header.cpp")
    

    header.cpp的内容如下:

    #include "header.h"
    

    上面的设置,就可以很好地处理各种情况下的预编译处理,追加的header.cpp也告诉了 xmake:header.h是作为 c++来预编译的。

    相对于经典的 vc 工程中的stdafx.cppstdafx.h,也能完美支持:

    target("test")
        set_precompiled_header("stdafx.h", "stdafx.cpp")
    

    生成 compiler_commands 插件

    扩展xmake project工程生成插件,支持compiler_commands.json文件输出,用于导出每个源文件的编译信息,生成基于 clang 的编译数据库文件,json 格式,可用于跟 ide,编辑器,静态分析工具进行交互。

    $ xmake project -k compile_commands
    

    输出的内容格式如下:

    [
      { "directory": "/home/user/llvm/build",
        "command": "/usr/bin/clang++ -Irelative -DSOMEDEF=\"With spaces, quotes and \\-es.\" -c -o file.o file.cc",
        "file": "file.cc" },
      ...
    ]
    
    

    一般用于跟 IDE、编辑器插件、静态分析工具进行集成,对于compile_commands的详细说明见:JSONCompilationDatabase

    自定义选项检测脚本

    在选项检测之前,动态增加一些配置条件:

    option("zlib")
        before_check(function (option)
            import("lib.detect.find_package")
            option:add(find_package("zlib"))
        end)
    

    通过覆写检测脚本,控制选项的检测结果:

    option("test")
        add_deps("small")
        set_default(true)
        on_check(function (option)
            if option:dep("small"):enabled() then
                option:enable(false)
            end
        end)
    

    如果 test 依赖的选项通过,则禁用 test 选项。

    在选项检测完成后,执行此脚本做一些后期处理,也可以在此时重新禁用选项:

    option("test")
        add_deps("small")
        add_links("pthread")
        after_check(function (option)
            option:enable(false)
        end)
    

    自定义目标加载脚本

    在 target 初始化加载的时候,将会执行on_load,在里面可以做一些动态的目标配置,实现更灵活的目标描述定义,例如:

    target("test")
        on_load(function (target)
            target:add("defines", "DEBUG", "TEST=\"hello\"")
            target:add("linkdirs", "/usr/lib", "/usr/local/lib")
            target:add({includedirs = "/usr/include", "links" = "pthread"})
        end)
    

    可以在on_load里面,通过target:set, target:add 来动态添加各种 target 属性。

    目标自定义构建脚本支持分平台架构

    通过设置平台|架构参数,控制自定义脚本的执行条件,实现在不同平台、架构下,调用不同的脚本进行构建:

    target("test")
        on_build("iphoneos|arm*", function (target) 
            -- TODO
        end)
    

    或者对所有 macosx 平台,执行脚本:

    target("test")
        after_build("macosx", function (target) 
            -- TODO
        end)
    

    其他脚本,例如:on_clean, before_package等也都是支持的哦,而在 2.1.4 之前,只支持:

    target("test")
        on_package(function (target) 
            -- TODO
        end)
    

    并不能对不同架构、平台分别处理。

    获取内置变量的值

    内置变量可以通过此接口直接获取,而不需要再加$()的包裹,使用更加简单,例如:

    print(val("host"))
    print(val("env PATH"))
    local s = val("shell echo hello")
    

    而用vformat就比较繁琐了:

    local s = vformat("$(shell echo hello)")
    

    不过vformat支持字符串参数格式化,更加强大,所以应用场景不同。

    目标依赖实现属性继承

    2.1.4 之前的版本,target.add_deps仅用于添加依赖,修改编译顺序:

    target("test1")
        set_kind("static")
        set_files("*.c")
    
    target("test2")
        set_kind("static")
        set_files("*.c")
    
    target("demo")
        add_deps("test1", "test2")
        add_links("test1", "test2")
        add_linkdirs("test1dir", "test2dir")
    

    2.1.5 版本后,target 还会自动继承依赖目标中的配置和属性,不再需要额外调用add_links, add_includedirsadd_linkdirs等接口去关联依赖目标了,上述代码可简化为:

    target("test1")
        set_kind("static")
        set_files("*.c")
    
    target("test2")
        set_kind("static")
        set_files("*.c")
    
    target("demo")
        add_deps("test1", "test2") -- 会自动链接依赖目标
    

    并且继承关系是支持级联的,例如:

    target("library1")
        set_kind("static")
        add_files("*.c")
        add_headers("inc1/*.h")
    
    target("library2")
        set_kind("static")
        add_deps("library1")
        add_files("*.c")
        add_headers("inc2/*.h")
    
    target("test")
        set_kind("binary")
        add_deps("library2")
    

    新增查找工具接口

    lib.detect.find_tool接口用于查找可执行程序,比lib.detect.find_program更加的高级,功能也更加强大,它对可执行程序进行了封装,提供了工具这个概念:

    • toolname: 工具名,可执行程序的简称,用于标示某个工具,例如:gcc, clang
    • program: 可执行程序命令,例如:xcrun -sdk macosx clang

    lib.detect.find_program只能通过传入的原始 program 命令或路径,去判断该程序是否存在。 而find_tool则可以通过更加一致的 toolname 去查找工具,并且返回对应的 program 完整命令路径,例如:

    import("lib.detect.find_tool")
    
    local tool = find_tool("clang")
    

    我们也可以指定{version = true}参数去获取工具的版本,并且指定一个自定义的搜索路径,也支持内建变量和自定义脚本哦:

    local tool = find_tool("clang", {check = "--help"}) 
    local tool = find_tool("clang", {check = function (tool) os.run("%s -h", tool) end})
    local tool = find_tool("clang", {version = true, {pathes = {"/usr/bin", "/usr/local/bin", "$(env PATH)", function () return "/usr/xxx/bin" end}})
    

    最后总结下,find_tool的查找流程:

    1. 优先通过{program = "xxx"}的参数来尝试运行和检测。
    2. 如果在xmake/modules/detect/tools下存在detect.tools.find_xxx脚本,则调用此脚本进行更加精准的检测。
    3. 尝试从/usr/bin/usr/local/bin等系统目录进行检测。

    我们也可以在工程xmake.luaadd_moduledirs指定的模块目录中,添加自定义查找脚本,来改进检测机制:

    projectdir
      - xmake/modules
        - detect/tools/find_xxx.lua
    

    更加安全的 root 权限编译

    由于 xmake 提供强大的自定义模块和脚本支持,并且内置安装、卸载等 action,如果xmake.lua里面的脚本描述不当,容易导致覆写系统文件,因此新版本对此作了改进:

    1. 在 root 下编译工程,先判断工程目录的用户权限属性,尝试降权到非 root 用户进行编译。
    2. 如果需要写一些系统文件,会提示用户当前权限不安全,禁止继续运行,除非加--root参数强制 root 运行。
    3. 如果当期工程目录是 root 用户权限,则同 2。

    具体详情见:pull#113

    API 接口改进

    使用includes替代老的add_subdirsadd_subfiles接口。

    使用set_config_header替代老的set_config_hset_config_h_prefix接口。

    新增大量扩展模块

    • 文件下载
    • 解压缩
    • git 操作等接口

    具体详情见文档:扩展模块

    1 条回复    2017-07-31 16:23:25 +08:00
    meepo3927
        1
    meepo3927  
       2017-07-31 16:23:25 +08:00
    前端表示不懂, 帮顶
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1101 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 1160ms · UTC 19:03 · PVG 03:03 · LAX 11:03 · JFK 14:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.