XMake - 基于 XML 的控制台构建系统





3.00/5 (9投票s)
2002年6月6日
15分钟阅读

111128

993
一篇关于C/C++项目新构建工具的文章
引言
那么,为什么还要另一个构建工具呢?这确实是个好问题,不是吗!基本上,我讨厌 make 文件以及整个 make 文件语法。我觉得它非常难读,编辑起来容易出错,在我看来简直一团糟。显然,我不是唯一这么想的人,因为有许多项目都在创建 make 的替代品(比如 Java 的 Ant,以及 Jam,这些都立刻浮现在脑海中)。
所以,既然 VCF 现在可以在 Linux/Unix 系统上构建了(到目前为止,FoundationKit 已经在 Linux 和 Solaris 上构建),我认为最好能有一个单一的构建文件,可以在任何系统上用于 GCC、VC++ 等。由于 XML 语法易于阅读(与传统的 make 文件相比),并且程序的范围会相当有限,即它被设计用来编译和链接 C++ 程序,而不会做太多其他事情,我认为值得花精力尝试编写一个类似 make 的程序,它能接受这种新语法。结果发现这并不难,可能花了两天的编程时间(加上可能一周的测试)来编写 xmake
,所以我觉得是值得的。
编写 xmake
的另一个原因是希望其他开发者能够快速地构建框架,即使是在没有良好 IDE 的系统上。我觉得向有 VC++ 和 GUI IDE 背景的开发者解释一套简单的 XML 标签,比费力解释 makefile 要容易得多。
虽然还有一些功能尚未完成,特别是 preBuild/postBuild 标签和项目级别的依赖关系,但 xmake 的核心功能已经相当不错了,我一直在定期使用它来构建 VCF 在 Linux 上的移植版本。这包括构建 FoundationKit 作为共享库,以及用于测试移植中各个部分的各种测试项目。
除了命令行工具外, xmake
引擎还将成为 VCFBuilder 的底层构建工具,因此 VCFBuilder 工作区/项目将共享与 xmake
相同的 XML 语法(尽管 VCFBuilder 会包含一些额外的标签,而这些标签会被 xmake
引擎忽略)。
基本用法
xmake
被设计成处理一个或多个项目。一个项目通常会产生一个单独的二进制输出,例如可执行文件(.exe)或库(.lib)或动态库(.dll 或 .so,取决于你的系统)。在一个项目中,你可以指定一个或多个配置来构建。例如,你可以有一个项目构建 "Debug GCC" 和 "Debug VC++" 和 "Debug BCC"(请注意,配置名称可以随意命名),它会使用不同的编译器和链接器来构建一个二进制文件,具体取决于配置。配置保存了编译器和链接器的绝大多数关键设置,同时还允许你为特殊文件类型指定额外的构建工具(例如,在 Win32 系统上为 .RC 文件指定的资源编译器)。 因此,在使用 xmake
时,你会告诉它在 make 文件(通常是 makefile.xml)中构建指定项目的一个特定配置。 xmake
足够智能,可以为你自动检查文件依赖关系,所以如果你最近修改了一个头文件,而你的项目中的五个源文件中有两个依赖于它,它只会构建受影响的两个源文件。
项目还包含一个或多个源文件的列表,这些源文件是构建项目所必需的。这些源文件包含诸如文件名(确切路径会动态确定,相对于调用 xmake
的路径)、输出名称、文件是否应该被构建(这很有用,可以让你 "关闭" 某些文件),以及最后该源文件所属的配置等信息。源文件可以属于一个或多个配置,每个配置名称用 "|" 字符分隔(例如,"Debug GCC|Debug VC++"
)。
命令行用法是
xmake [projectName] -config configurationName [-f makefileName] || [-install] || [-clean]
你可以从命令行调用 xmake
程序。你可以通过以下选项来控制程序:
projectName
- 这是此 make 文件中要构建的项目的名称。如果留空,则构建找到的第一个项目。-config configurationName
- 这告诉xmake
程序要构建哪个配置。-config
选项 **必须** 后面跟一个配置名称,这样xmake
才知道要构建什么。如果指定了错误的名称,你就无法构建预期的内容。-f makefileName
- 要使用的 make 文件。如果未指定此选项,则xmake
会在当前目录中查找名为 "makefile.xml" 的文件。如果找不到,则会产生错误并退出xmake
。-install
- 当前未实现-clean
- 删除 make 文件为指定配置生成的所有二进制文件
举例说明其用法,例如,要在 Linux 上使用 GCC 构建 VCF 的 FoundationKit,如下所示:
xmake -config "GCC Debug"
XML 语法
由于 xmake
make 文件基于 XML,因此它遵循所有 XML 语法规则。请注意,如果你需要在值中使用双引号,请使用单引号,它将在构建过程中被替换为双引号。
<make>
这是最外层的标签。每个 xmake
make 文件都必须以这个标签开始和结束。
<substitutions>
在这里,你可以指定变量名,这些变量将在 make 文件被 xmake
解析和执行时被替换。例如,你可以指定一个通用的 include 路径,或者一个通用的输出文件夹。在 substitutions
标签内,可以有零个或多个变量标签,它们有两个属性:name 和 value。name 属性是变量在 make 文件中引用的名称,value 属性指定了当解析器遇到变量时将被替换的字符串。系统环境变量也可以使用,所以如果你定义了 VCF_INCLUDE
作为你的某个系统环境变量,那么解析器足够智能,会尝试检查该变量是否存在。
要引用变量,请按以下方式进行:
- $(<variable name>),例如,如果你定义了一个名为 MyIncludePath 的变量,那么你可以这样引用它:$(MyIncludePath)
- %<variable name>%,例如,如果你定义了一个名为 MyIncludePath 的变量,那么你可以这样引用它:%MyIncludePath%
这个标签部分的示例看起来像这样:
<substitutions>
<variable name="VCF_INCLUDE" value="e:\code\vcf\include"/>
<variable name="INC" value="../../../include"/>
<variable name="SRC" value="../../../src"/>
</substitutions>
**请注意**:变量可以在指定属性值的任何地方使用。
<project>
项目包含所有重要的节点。它还具有一系列属性,如下所示:
name
- 项目的名称path
- 项目的路径。此项目前是可选的,作用不大...outputAs
- 项目的输出路径。这用于确定代表项目成功构建的二进制文件,并在你 "clean" 项目时指定要删除的二进制文件。例如,它可以是 "FooBar.exe"、"MyLib.dll" 或 "MySharedCode.so"。
请注意,outputAs
属性是可选的。大多数编译器/链接器都可以指定二进制输出的位置。然而,如上所述,输出名称用于 xmake
完成项目的 "clean" 操作。
<dependencies>
尚未实现。这将允许指定在当前项目之前必须构建哪些项目。同样,这与 VC++ 中的依赖关系工作方式非常相似。
<configurations>
配置是 xmake
的核心。如果你使用过 Microsoft 的 Visual C++,那么你会在这里感到很熟悉。基本上,配置是用于构建项目的一整套编译器和链接器设置。一个项目可以有多个配置,例如,你可能有 "Win32 Debug"、"Win32 Release"、"Linux Debug"、"Linux Release"、"Mac Debug" 和 "Mac Release"。因此,为了让 xmake
完成它的工作,它需要知道你想构建的配置。<configurations>
标签用于指示集合,然后是每个配置的 1 个或多个 <config>
子标签。配置具有以下属性:
name
- 配置名称用于标识配置,并且源文件使用它来标识它们属于哪个配置。配置名称只能包含字符 [a..z, A..Z, 0..9],"|" 用于分隔多个配置。srcBinaryExt
- srcBinaryExt 用于标识编译后的源文件扩展名。许多编译器使用 ".o",而许多其他编译器使用 ".obj"。例如,GCC/G++ 在其对象文件扩展名中使用 ".o",而 VC++ 使用 ".obj"。这允许你只指定输出文件的名称(不带 "." 扩展名),让xmake
根据当前正在构建的配置附加正确的扩展名。
示例看起来大致如下:
<config name="VC++ Debug" srcBinaryExt=".obj">
.... more stuff follows
除了上述属性外,配置还包含以下子标签:
includes
- includes 部分通知xmake
在哪里查找不在当前目录中的 include 文件。这在源文件的依赖关系扫描期间使用。tools
- tools 部分允许你为无法使用<samp>compiler</samp>
和配置中的linker
进行编译的源文件指定特殊的构建工具。compiler
- 这指定了用于编译源文件(除非源文件指定了另一个tool
)的编译器。linker
- 这指定了用于创建最终项目二进制文件的链接器。preBuild
- 尚未实现。postBuild
- 尚未实现。
<includes>
这允许你指定一组目录,在解析构建依赖项时从中查找。每个 include 目录都需要一个 include 标签。每个 include 标签有一个单独的属性:
path
- path 是一个目录路径,它将被添加到用于确定依赖关系的搜索路径列表中。
示例看起来大致如下:
<includes>
<include path="../../MyDir/includes"/>
</includes>
.... more stuff follows
默认情况下,在依赖项检查中会搜索源文件所在的目录。
<tools>
tools 标签允许你指定 0 个或多个工具,这些工具可以用来构建特定类型的文件。工具与源文件的关联是通过源文件的 "tool" 属性与工具的 "id" 属性具有相同的值来实现的。每个工具在 <tool>
标签中描述,并具有以下属性:
name
- 工具可执行文件的名称,例如 "rc.exe"。你可以指定完整路径或相对路径。id
- 工具的文本名称。这是各种源条目将引用工具的名称。它可以是任何你想要的名字,只要你保持一致地引用它。
每个 tool 标签都可以关联标志。这是通过使用 <flags>
标签来指示集合,然后使用 <flag>
子标签。每个 flag 具有以下属性:
- value - 你想传递给工具的文本参数
然后将这些标志一个接一个地附加起来,创建一个命令行,在需要时发送给工具。
示例看起来大致如下:
<tools>
<tool name="rc.exe" id="rc" > <!-- here we are using just the resource compilers file
name, assuming it can be found on the system path -->
<flags>
<flag value="/i'..\..\MyResIncludes' \d '_DEBUG'"/>
</flags>
</tool>
</tools>
.... more stuff follows
<compiler>
compiler 标签允许你指定用作编译器的可执行文件(针对特定配置)以及在处理源文件时要发送给编译器的标志集。 compiler 标签只有一个属性:
- name - 用于编译源文件的可执行文件的相对或完整路径名。如果名称只是短名称(例如 "cl.exe"),则假定它可以在系统路径中找到。
编译器可以有关联的标志。这是通过使用 <flags>
标签来指示集合,然后使用 <flag>
子标签。每个 flag 具有以下属性:
- value - 你想传递给编译器的文本参数
然后将这些标志一个接一个地附加起来,创建一个命令行,在每次调用编译器处理每个源文件时发送给它。
示例看起来大致如下:
<compiler name="cl">
<flags>
<flag value="/I $(INC)"/>
<flag value="/nologo /MDd /W3 /Gm /GR /GX /ZI /Od"/>
<flag value="/D WIN32 /D _DEBUG /D _CONSOLE /D _MBCS /D FRAMEWORK_DLL /D FRAMEWORK_EXPORTS"/>
<flag value="/c"/>
</flags>
</compiler >
.... more stuff follows
请注意第一个标志中使用了变量 INC
。另外,你也可以把所有内容放在一个标志中,但将其分开会更容易让其他人阅读。
请注意:在需要双引号的地方使用单引号。在解析字符串并准备发送给编译器时,单引号字符将被扩展为双引号。
<linker>
linker 标签允许你指定用作链接器的可执行文件(针对特定配置)以及在链接所有编译器对象代码时要发送给链接器的标志集。 linker 标签只有一个属性:
- name - 用于链接对象文件的可执行文件的相对或完整路径名。如果名称只是短名称(例如 "link.exe"),则假定它可以在系统路径中找到。
链接器可以有关联的标志。这是通过使用 <flags>
标签来指示集合,然后使用 <flag>
子标签。每个 flag 具有以下属性:
- value - 你想传递给链接器的文本参数
然后将这些标志一个接一个地附加起来,创建一个命令行,在调用链接器以构建项目最终二进制映像时发送给它。
示例看起来大致如下:
<linker name="link.exe">
<flags>
<flag value="/nologo"/>
<flag value="/subsystem:console"/>
<flag value="/incremental:yes"/>
<flag value="/pdb:'Debug/xmake.pdb'"/>
<flag value="/debug"/>
<flag value="/machine:I386"/>
<flag value="/out:'Debug/xmake.exe'"/>
<flag value="/pdbtype:sept"/>
</flags>
</linker>
.... more stuff follows
请注意:在需要双引号的地方使用单引号。在解析字符串并准备发送给链接器时,单引号字符将被扩展为双引号。
<preBuild>
目前尚未实现。实现后,它将允许你指定一系列命令行操作,在构建过程的编译阶段 **之前** 执行。preBuild 是 configuration 标签的一部分,因此与特定配置相关联。
拟议的语法可能会像这样:
<prebuild>
<exec commandLine="copy Foo.cpp ../../outputSrc"/>
<!-- ...other commands -->
</prebuild>
.... more stuff follows
<postBuild>
目前尚未实现。实现后,它将允许你指定一系列命令行操作,在构建过程的链接阶段 **之后** 执行。postBuild 是 configuration 标签的一部分,因此与特定配置相关联。
拟议的语法可能会像这样:
<postbuild>
<!-- in this example we use doxygen to autogenerate
source documentation-->
<exec commandLine="doxygen ../../help/Doxyfile"/>
<!-- ...other commands -->
</postbuild>
.... more stuff follows
<sources>
在指定完所有配置后,列出构建项目所需的源文件。sources 标签是一个包含 1 个或多个 source 子标签的集合,每个 source 子标签代表一个要构建的单独源文件。source 标签具有以下属性:
name
- 源文件的名称,相对于项目构建目录。此属性是必需的。partOfConfig
- 这表明源文件将在哪个配置下编译。配置的名称 **必须** 与 make 文件中前面定义的某个 config 部分中指定的配置名称完全匹配。如果源文件可以在多个配置下编译,那么每个配置名称必须用 "|" 字符分隔。此属性是必需的。build
- 这表示是否应该为该配置构建该文件。此属性的可能值为 "yes" 或 "no"。此属性是可选的。如果不指定,则默认为 "yes",意味着文件将被编译。outputAs
- 这告诉xmake
编译后的对象文件应该放在哪里。这将覆盖编译器标志中的设置。通常你希望两者匹配。通过指定此属性,xmake
知道在 "clean" 时在哪里清理对象文件。此属性是可选的。如果不指定,则默认为项目 make 文件所在的目录。在指定输出文件名时,可以省略扩展名(如 foo.o 或 baz.obj),xmake 会从配置的srcBinaryExt
属性中确定正确的扩展名。tool
- 告诉xmake
该源文件不是使用默认编译器编译的,而是使用 "tool" 属性值与此属性值匹配的工具进行 "编译"。这两个值必须完全匹配。如果在 make 文件中没有具有匹配id
属性的工具,则该源文件不会被编译。此属性是必需的。
示例看起来大致如下:
<sources>
<source name="Test1Make.rc" partOfConfig="VC++ Debug"
outputAs="Debug/Test1Make.res" tool="rc"/>
<source name="StdAfx.cpp" partOfConfig="VC++ Debug" />
<source name="MainFrm.cpp" partOfConfig="VC++ Debug"
outputAs="Debug/MainFrm.obj"/>
<source name="ChildView.cpp" partOfConfig="VC++ Debug"
outputAs="Debug/ChildView.obj"/>
<source name="Test1Make.cpp" partOfConfig="VC++ Debug"
outputAs="Debug/Test1Make.obj"/>
</sources>
请注意第一个 source 标签中 tool 属性的使用。
构建和安装 xmake
你可以在 Source Forge VCF 项目页面 这里 获取 xmake
。你可以使用包含的 VC++ 工作区为 Win32 系统构建。或者,可以使用 GCC 在 'nix 系统上使用传统的 configure
、make
、make install
命令来构建(提供了 configure 和 Makefile)。
它已经在 Windows 2000、Windows NT sp4(它是一个相当简单的命令行工具,所以应该可以在 Windows 98 和 Windows XP 中正常工作)、Linux 2.4(RH7.1 发行版)、SparcStation with Solaris 8 (2.8) 上构建和测试过,并且我听说它可以在 MacOSX 和 VMS 上运行,但我尚未亲自验证。
要在 Win32 系统上安装它,只需将可执行文件放在你想要的位置,最好是放在你的系统路径上的某个地方。对于 'nix 系统,make install
命令会将其放在 /usr/bin
,或者你可以将其放在其他地方。
致谢
我想感谢 Chris Losinger 提供的 CCmdLine
类,我用它来解析命令行。
我还想感谢 Peter Sulyok,他编写了文件依赖性检查器的原始代码,我借鉴并进行了一些修改。这最初是在 MAKEDEP.C 文件中,版权所有 © 1994 Peter Sulyok。