描述语法

xmake的描述语法基于lua实现,因此描述语法继承了lua的灵活性和简洁性,并且通过28原则,将描述作用域(简单描述)、脚本作用域(复杂描述)进行分离,使得工程更加的简洁直观,可读性非常好。

因为80%的工程,并不需要很复杂的脚本控制逻辑,只需要简单的几行配置描述,就可满足构建需求,基于这个假设,xmake分离作用域,使得80%的xmake.lua文件,只需要这样描述:

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/*.c")

而仅有的20%的工程,才需要这样描述:

  1. target("demo")
  2. set_kind("shared")
  3. set_objectdir("$(buildir)/.objs")
  4. set_targetdir("libs/armeabi")
  5. add_files("jni/*.c")
  6. on_package(function (target)
  7. os.run("ant debug")
  8. end)
  9. on_install(function (target)
  10. os.run("adb install -r ./bin/Demo-debug.apk")
  11. end)
  12. on_run(function (target)
  13. os.run("adb shell am start -n com.demo/com.demo.DemoTest")
  14. os.run("adb logcat")
  15. end)

上面的function () end部分属于自定义脚本域,一般情况下是不需要设置的,只有在需要复杂的工程描述、高度定制化需求的情况下,才需要自定义他们,在这个作用域可以使用各种xmake提供的扩展模块,关于这个的更多介绍,见:xmake 描述语法和作用域详解

而上面的代码,也是一个自定义混合构建jni和java代码的android工程,可以直接通过xmake run命令,实现一键自动构建、安装、运行apk程序。

下面介绍一些比较常用的xmake描述实例:

构建一个可执行程序

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/*.c")

这是一个最简单经典的实例,一般情况下,这种情况,你不需要自己写任何xmake.lua文件,在当前代码目录下,直接执行xmake命令,就可以完成构建,并且会自动帮你生成一个xmake.lua

关于自动生成的详细信息,见:xmake智能代码扫描编译模式,无需手写任何make文件

构建一个可配置切换的库程序

  1. target("demo")
  2. set_kind("$(kind)")
  3. add_files("src/*.c")

可通过配置,切换是否编译动态库还是静态库:

  1. $ xmake f --kind=static; xmake
  2. $ xmake f --kind=shared; xmake

增加debug和release编译模式支持

也许默认的几行描述配置,已经不能满足你的需求,你需要可以通过切换编译模式,构建debug和release版本的程序,那么只需要:

  1. if is_mode("debug") then
  2. set_symbols("debug")
  3. set_optimize("none")
  4. end
  5. if is_mode("release") then
  6. set_symbols("hidden")
  7. set_optimize("fastest")
  8. set_strip("all")
  9. end
  10. target("demo")
  11. set_kind("binary")
  12. add_files("src/*.c")

你只需要通过配置来切换构建模式:

  1. $ xmake f -m debug; xmake
  2. $ xmake f -m release; xmake

[-m|--mode]属于内置选项,不需要自己定义option,就可使用,并且模式的值是用户自己定义和维护的,你可以在is_mode("xxx")判断各种模式状态。

通过自定义脚本签名ios程序

ios的可执行程序,在设备上运行,需要在构建完成后进行签名,这个时候就可以使用自定义脚本来实现:

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/*.m")
  4. after_build(function (target))
  5. os.run("ldid -S %s", target:targetfile())
  6. end

这里只是用ldid程序做了个假签名,只能在越狱设备上用哦,仅仅作为例子参考哈。

内置变量和外置变量

xmake提供了$(varname)的语法,来支持内置变量的获取,例如:

  1. add_cxflags("-I$(buildir)")

它将会在在实际编译的时候,将内置的buildir变量转换为实际的构建输出目录:-I./build

一般内置变量可用于在传参时快速获取和拼接变量字符串,例如:

  1. target("test")
  2. add_files("$(projectdir)/src/*.c")
  3. add_includedirs("$(buildir)/inc")

也可以在自定义脚本的模块接口中使用,例如:

  1. target("test")
  2. on_run(function (target)
  3. os.cp("$(scriptdir)/xxx.h", "$(buildir)/inc")
  4. end)

当然这种变量模式,也是可以扩展的,默认通过xmake f --var=val命令,配置的参数都是可以直接获取,例如:

  1. target("test")
  2. add_defines("-DTEST=$(var)")

既然支持直接从配置选项中获取,那么当然也就能很方便的扩展自定义的选项,来获取自定义的变量了,具体如何自定义选项见:option

修改目标文件名

我们可以通过内建变量,将生成的目标文件按不同架构和平台进行分离,例如:

  1. target("demo")
  2. set_kind("binary")
  3. set_basename("demo_$(arch)")
  4. set_targetdir("$(buildir)/$(plat)")

之前的默认设置,目标文件会生成为build\demo,而通过上述代码的设置,目标文件在不同配置构建下,路径和文件名也不尽相同,执行:

  1. $ xmake f -p iphoneos -a arm64; xmake

则目标文件为:build/iphoneos/demo_arm64

添加子目录工程模块

如果你有多个target子模块,那么可以在一个xmake.lua中进行定义,例如:

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/demo.c")
  4. target("test")
  5. set_kind("binary")
  6. add_files("src/test.c")

但是,如果子模块非常多,那么放置在一个xmake文件,就显得有些臃肿了,可以放置到独立模块的子目录去:

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/demo.c")
  4. add_subdirs("src/test")

通过上述代码,关联一个子工程目录,在里面加上test的工程目标就行了。

安装头文件

  1. target("tbox")
  2. set_kind("static")
  3. add_files("src/*.c")
  4. add_headers("../(tbox/**.h)|**/impl/**.h")
  5. set_headerdir("$(buildir)/inc")

安装好的头文件位置和目录结构为:build/inc/tbox/*.h

其中../(tbox/**.h)带括号的部分,为实际要安装的根路径,|**/impl/**.h部分用于排除不需要安装的文件。

其通配符匹配规则、排除规则可参考add_files

多目标依赖构建

多个target工程目标,默认构建顺序是未定义的,一般按顺序的方式进行,如果你需要调整构建顺序,可以通过添加依赖顺序来实现:

  1. target("test1")
  2. set_kind("static")
  3. set_files("*.c")
  4. target("test2")
  5. set_kind("static")
  6. set_files("*.c")
  7. target("demo")
  8. add_deps("test1", "test2")
  9. add_links("test1", "test2")

上面的例子,在编译目标demo的时候,需要先编译test1, test2目标,因为demo会去用到它们。

合并静态库

xmake的add_files接口功能是非常强大的,不仅可以支持多种语言文件的混合添加构建,还可以直接添加静态库,进行自动合并库到当前的工程目标中去。

我们可以这么写:

  1. target("demo")
  2. set_kind("static")
  3. add_files("src/*.c", "libxxx.a", "lib*.a", "xxx.lib")

直接在编译静态库的时候,合并多个已有的静态库,注意不是链接哦,这跟add_links是有区别的。

并且你也可以直接追加对象文件:

  1. target("demo")
  2. set_kind("binary")
  3. add_files("src/*.c", "objs/*.o")

添加自定义配置选项

我们可以自己定义一个配置选项,例如用于启用test:

  1. option("test")
  2. set_default(false)
  3. set_showmenu(true)
  4. add_defines("-DTEST")

然后关联到指定的target中去:

  1. target("demo")
  2. add_options("test")

这样,一个选项就算定义好了,如果这个选项被启用,那么编译这个target的时候,就会自动加上-DTEST的宏定义。

上面的设置,默认是禁用test选项的,接下来我们通过配置去启用这个选项:

  1. $ xmake f --test=y
  2. $ xmake

xmake的选项支持是非常强大的,除了上述基础用法外,还可以配置各种检测条件,实现自动检测,具体详情可参考:option依赖包的添加和自动检测机制

添加第三方依赖包

在target作用域中,添加集成第三方包依赖,例如:

  1. target("test")
  2. set_kind("binary")
  3. add_packages("zlib", "polarssl", "pcre", "mysql")

这样,在编译test目标时,如果这个包存在的,将会自动追加包里面的宏定义、头文件搜索路径、链接库目录,也会自动链接包中所有库。

用户不再需要自己单独调用add_linksadd_includedirs, add_ldflags等接口,来配置依赖库链接了。

对于如何设置包搜索目录,可参考add_packagedirs接口,依赖包详情请参考:依赖包的添加和自动检测机制

生成配置头文件

如果你想在xmake配置项目成功后,或者自动检测某个选项通过后,把检测的结果写入配置头文件,那么需要调用这个接口来启用自动生成config.h文件。

使用方式例如:

  1. target("test")
  2. set_config_h("$(buildir)/config.h")
  3. set_config_h_prefix("TB_CONFIG")

当这个target中通过下面的这些接口,对这个target添加了相关的选项依赖、包依赖、接口依赖后,如果某依赖被启用,那么对应的一些宏定义配置,会自动写入被设置的config.h文件中去。

这些接口,其实底层都用到了option选项中的一些检测设置,例如:

  1. option("wchar")
  2. -- 添加对wchar_t类型的检测
  3. add_ctypes("wchar_t")
  4. -- 如果检测通过,自动生成TB_CONFIG_TYPE_HAVE_WCHAR的宏开关到config.h
  5. add_defines_h_if_ok("$(prefix)_TYPE_HAVE_WCHAR")
  6. target("test")
  7. -- 启用头文件自动生成
  8. set_config_h("$(buildir)/config.h")
  9. set_config_h_prefix("TB_CONFIG")
  10. -- 添加对wchar选项的依赖关联,只有加上这个关联,wchar选项的检测结果才会写入指定的config.h中去
  11. add_options("wchar")

检测库头文件和接口

我们可以在刚刚生成的config.h中增加一些库接口检测,例如:

  1. target("demo")
  2. -- 设置和启用config.h
  3. set_config_h("$(buildir)/config.h")
  4. set_config_h_prefix("TEST")
  5. -- 仅通过参数一设置模块名前缀
  6. add_cfunc("libc", nil, nil, {"sys/select.h"}, "select")
  7. -- 通过参数三,设置同时检测链接库:libpthread.a
  8. add_cfunc("pthread", nil, "pthread", "pthread.h", "pthread_create")
  9. -- 通过参数二设置接口别名
  10. add_cfunc(nil, "PTHREAD", nil, "pthread.h", "pthread_create")

生成的config.h结果如下:

  1. #ifndef TEST_H
  2. #define TEST_H
  3. // 宏命名规则:$(prefix)前缀 _ 模块名(如果非nil)_ HAVE _ 接口名或者别名 (大写)
  4. #define TEST_LIBC_HAVE_SELECT 1
  5. #define TEST_PTHREAD_HAVE_PTHREAD_CREATE 1
  6. #define TEST_HAVE_PTHREAD 1
  7. #endif

这样我们在代码里面就可以根据接口的支持力度来控制代码编译了。

自定义插件任务

task域用于描述一个自定义的任务实现,与targetoption同级。

例如,这里定义一个最简单的任务:

  1. task("hello")
  2. -- 设置运行脚本
  3. on_run(function ()
  4. print("hello xmake!")
  5. end)

这个任务只需要打印hello xmake!,那如何来运行呢?

由于这里没有使用set_menu设置菜单,因此这个任务只能在xmake.lua的自定义脚本或者其他任务内部调用,例如:

  1. target("test")
  2. after_build(function (target)
  3. -- 导入task模块
  4. import("core.project.task")
  5. -- 运行hello任务
  6. task.run("hello")
  7. end)

此处在构建完test目标后运行hello任务,当然我们还可以传递参数哦:

  1. task("hello")
  2. on_run(function (arg1, arg2, arg3)
  3. print("hello xmake!", arg1, arg2, arg3)
  4. end)
  5. target("test")
  6. after_build(function (target)
  7. import("core.project.task")
  8. task.run("hello", {}, "arg1", "arg2", "arg3")
  9. end)

上述task.run{}这个是用于传递插件菜单中的参数,这里没有通过set_menu设置菜单,此处传空。

xmake的插件支持也是功能很强大的,并且提供了很多内置的使用插件,具体请参考:xmake插件手册task手册

或者可以参考xmake自带的一些插件demo

另外一种语法风格

xmake除了支持最常使用的set-add描述风格外,还支持另外一种语法风格:key-val,例如:

  1. target
  2. {
  3. name = "test",
  4. defines = "DEBUG",
  5. files = {"src/*.c", "test/*.cpp"}
  6. }

这个等价于:

  1. target("test")
  2. set_kind("static")
  3. add_defines("DEBUG")
  4. add_files("src/*.c", "test/*.cpp")

用户可以根据自己的喜好来选择合适的风格描述,但是这边的建议是:

  1. * 针对简单的工程,不需要太过复杂的条件编译,可以使用key-val方式,更加精简,可读性好
  2. * 针对复杂工程,需要更高的可控性,和灵活性的话,建议使用set-add方式
  3. * 尽量不要两种风格混着写,虽然是支持的,但是这样对整个工程描述会感觉很乱,因此尽量统一风格作为自己的描述规范

另外,不仅对target,像option, task, template都是支持两种方式设置的,例如:

  1. -- set-add风格
  2. option("demo")
  3. set_default(true)
  4. set_showmenu(true)
  5. set_category("option")
  6. set_description("Enable or disable the demo module", " =y|n")
  1. -- key-val风格
  2. option
  3. {
  4. name = "demo",
  5. default = true,
  6. showmenu = true,
  7. category = "option",
  8. desciption = {"Enable or disable the demo module", " =y|n"}
  9. }

自定义的任务或者插件可以这么写:

  1. -- set-add风格
  2. task("hello")
  3. on_run(function ()
  4. print("hello xmake!")
  5. end)
  6. set_menu {
  7. usage = "xmake hello [options]",
  8. description = "Hello xmake!",
  9. options = {}
  10. }
  1. -- key-val风格
  2. task
  3. {
  4. name = "hello",
  5. run = (function ()
  6. print("hello xmake!")
  7. end),
  8. menu = {
  9. usage = "xmake hello [options]",
  10. description = "Hello xmake!",
  11. options = {}
  12. }
  13. }

结语

更多描述说明,可直接阅读xmake的官方手册,上面提供了完整的api文档和使用描述。


个人主页:TBOOX开源工程

使用xmake优雅地描述工程的更多相关文章

  1. 优雅解决 SpringBoot 工程中多环境下 application.properties 的维护问题

    微信号:geekoftaste, 期待与大家一起探讨! 背景 我们知道 SpringBoot 有一个全局的配置文件 application.properties, 可以把工程里用到的占位符,第三方库的 ...

  2. xmake入门,构建项目原来可以如此简单

    前言 在开发xmake之前,我一直在使用gnumake/makefile来维护个人C/C++项目,一开始还好,然而等项目越来越庞大后,维护起来就非常吃力了,后续也用过一阵子automake系列工具,并 ...

  3. C/C++ 构建系统,我用 xmake

    XMake 是什么 XMake 是一个基于 Lua 的 现代化 C/C++ 构建系统. 它的语法简洁易上手,对新手友好,即使完全不会 lua 也能够快速入门,并且完全无任何依赖,轻量,跨平台. 同时, ...

  4. java工程中的.classpathaaaaaaaaaaaaaaaa<转载>

    第一部分:classpath是系统的环境变量,就是说JVM加载类的时候要按这个路径下去找,当然这个路径下可以有jar包,那么就是jar包里所有的class. eclipse build path是ec ...

  5. java工程中的.classpath<转载>

    第一部分:classpath是系统的环境变量,就是说JVM加载类的时候要按这个路径下去找,当然这个路径下可以有jar包,那么就是jar包里所有的class. eclipse build path是ec ...

  6. myeclipse工程当中的.classpath 和.project文件什么作用?

    .project是项目文件,项目的结构都在其中定义,比如lib的位置,src的位置,classes的位置.classpath的位置定义了你这个项目在编译时所使用的$CLASSPATH .classpa ...

  7. Sbt的使用初步和用sbt插件生成eclipse工程

    以前一直是用maven去管理java项目,现在开始写scala项目了但是在scala-ide中去编译scala项目和sbt的区别一直没弄清楚受到文章:http://my.oschina.net/yjw ...

  8. eclipse工程当中的.classpath 和.project文件什么作用?

    .project是项目文件,项目的结构都在其中定义,比如lib的位置,src的位置,classes的位置.classpath的位置定义了你这个项目在编译时所使用的$CLASSPATH .classpa ...

  9. 使用Git如何优雅的忽略掉一些不必的文件

    熟悉使用Git之后发现,使用sourceTree来管理和开发项目会变得更高效,现在我用bitbucket管理自己的项目,它提供了私有的仓库,用起来还是比较爽,不过刚开始用的时候,只要一打开本地仓库的工 ...

随机推荐

  1. Thinking in Annotation

    Thinking in Java这本书很久前就购买了,打算有时间看一下,因为自己的时间被自己安排的紧张,也没时间看书.黄师傅上次课程讲到了注解的使用和反射的使用,今天打算学习一下注解.该文章参考Thi ...

  2. jupyter notebook添加环境

    列出当前kernel: jupyter kernelspec list 删除已有环境:jupyter kernelspec remove NAME 安装新kernel ipython kernel i ...

  3. 前端之HTML:HTML

    前端基础之html 一.初始html 1.web服务本质 import socket sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc ...

  4. Django【第26篇】:中介模型以及优化查询以及CBV模式

    中介模型以及优化查询以及CBV模式 一.中介模型:多对多添加的时候用到中介模型 自己创建的第三张表就属于是中介模型 class Article(models.Model): ''' 文章表 ''' t ...

  5. 什么是LMDB闪电记忆映射数据库

    LightningMemory-MappedDatabase(LMDB)是一个软件库,它以键值存储的形式提供高性能的嵌入式事务数据库.LMDB是用C语言编写的,具有多种编程语言的API绑定.LMDB将 ...

  6. os模块、sys模块、json模块、pickle模块、logging模块

    目录 os模块 sys模块 json模块 pickle模块 logging模块 os模块 功能:与操作系统交互,可以操作文件 一.对文件操作 判断是否为文件 os.path.isfile(r'路径') ...

  7. CF786B Legacy 线段树优化建图 + spfa

    CodeForces 786B Rick和他的同事们做出了一种新的带放射性的婴儿食品(???根据图片和原文的确如此...),与此同时很多坏人正追赶着他们.因此Rick想在坏人们捉到他之前把他的遗产留给 ...

  8. POJ 3046 Ant Counting ( 多重集组合数 && 经典DP )

    题意 : 有 n 种蚂蚁,第 i 种蚂蚁有ai个,一共有 A 个蚂蚁.不同类别的蚂蚁可以相互区分,但同种类别的蚂蚁不能相互区别.从这些蚂蚁中分别取出S,S+1...B个,一共有多少种取法. 分析 :  ...

  9. Activiti创建表(三)

    创建Mysql 创建 mysql 数据库 activiti(名字任意):CREATE DATABASE activiti DEFAULT CHARACTER SET utf8; pom.xml < ...

  10. volley简介

    究竟什么是volley呢?  在以前的开发过程中,开发app的时候,使用的东西可能包括: 1.Httpclient,HttpURLConnection 2.AsyncTask,AsyncTaskLoa ...