HXCPP

1 Build

haxelib run hxcpp Build.xml -Dios

2 Metas

# TODO
@:abi                   # Function ABI/calling convention

# 嵌入 xml 到 build.xml, 文件格式参看下边 buildXml
@:buildXml              # Specify xml data to be injected into Build.xml

# 附加于 类 上. 代码注入于 namespace 的前边
@:cppFileCode           # Inject code into top of cpp file

# 附加于 类 上.. 在 .cpp 文件中头部添加 头文件引用, 注意: 只能添加一个
# 示例: @:cppInclude("stdlib.h") => #include "stdlib.h"
@:cppInclude            # File to be included in generated cpp file

# 附加于 类 上. 代码注入于 类 的前边.并包含当前类. 命名空间为 haxe 包名.
@:cppNamespaceCode      #

# __declspec 参看 hxcpp/include/hxcpp.h
@:decl                  #

# 注入代码到 函数开始处
@:functionCode          # Inject code into top of function - eg, whole implementation.

# 注入代码到 函数结尾处, 在 return 之前. 
# 注: 由于 haxe 转换成的 cpp 用 {} 包围形成了闭包, 因此无法访问函数内部定义的局部变量, 但是可以访问函数的参数
@:functionTailCode      # Inject code into end of function 

# 注入 cpp 代码到 头文件(.h) Class 的内部 - 用于声明 成员变量或成员方法 例如: @:headerClassCode("int a,b")
@:headerClassCode       # Code to be injected into the generated class, in the header

# 注入 cpp 代码到 头文件(.h) Class 的外部 - 用于 添加 头文件引用或 宏定义 例如: @:headerCode("#include <stdio.h>")
@:headerCode()          # Code to be injected into the generated header file

# 附加在 class 上, 在 头文件(.h) 中 注入 例如: @headerInclude("stdio.h") - 只能添加一个头文件
@:headerInclude(String) # File to be included in generated header file

@:headerNamespaceCode   # 头文件类声明, 类 的外部 命名空间内部

# @:include("path/file.h") - 用于 extern 类, 指示 这个类应该导入哪个 头文件 和 @:sourceFile 一起使用
@:include               # Generate "#include ..." to .h/.cpp where the class is being imported.

# TODO
@:nativeProperty        # Use native properties which will execute even with dynamic usage

# 编译的cpp代码中不包含有类似于 HX_STACK_LINE...这些调试用的代码, 只用于字段或方法(但是好像无法作用于构造函数),
@:noStack               # 

@:nonVirtual            # Declares function to be non-virtual in cpp (cpp only)

# @:sourceFile("path/file.cpp") - 用于 external class,路径以当前 hx 文件所在为当前目录,参考 hxcpp/test/native 目录
@:sourceFile            # Source code filename for external class

# 重要: 用于 extern 类, 
@:structAccess          # Marks an extern class as using struct access('.') not pointer('->')

# 如果你不需要使用 Reflect 来动态查找类上的成员, 则可以加上这个标记
@:unreflective          # not generate __Field and __SetField bodies by default

@:void                  # Use Cpp native 'void' return type (cpp only)

# 文件依赖, 也就是当依赖的文件发生过改动,将重将编译.
# 附加在 extern 类上, 哪个类调用了这个 extern 类, 就会在 build.xml 中相关 cpp 新增一条 <depend name="file.h" />
@:depend                # Add a dependency in the build.xml file.

3 build.Xml

<set name="var1" value="v1" if="D1" />          <!-- 如果 D1 存在, 则设置 var1=v1 -->
<set name="var2" value="v2" if="D1 D2" />       <!-- 如果 D1 与 D2 定义同时存在, ... -->
<set name="var3" value="v3" if="D1 || D2" />    <!-- 如果 D1 或 D2 定义存在, ... -->

<echo value="hello" unless="D1" />      <!-- 只有当 D1 不存在时, 才打印 hello , 支持:或(||),与(" ") -->
<echo value="world" ifExists="path/to/file" />  <!-- 检测文件或目录存在 -->
<echo value="${var1}" />
<echo value="hello ${var1}" />
<set name="" value="" />        <!-- 设置变量 name=value, 在其它元素中通过 ${name} 来引用 value 的值 -->
                                <!-- 可用于变量条件检测, 示例 -->
    <set name="someMsg" value="Hello world!" />
    <echo value="${someMsg}" if="someMsg" />                                

<unset name="" />               <!-- 移除变量 或 haxe -D -->

<!-- hxcpp编译设置, 细节查看 Setup.hx -->
<setup name="androidNdk|blackberry|msvc|mingw|emscripten" />                

<echo value="" />               <!-- log -->
<error value="" />              <!-- error, 将中断编译 -->

<setevn name="" value="" />     <!-- 设置环境变量, 通过 Sys.putEnv 阶段性(session)  -->

<path name="" />                <!-- 设置环境路径, 通过 Sys.putEnv("PATH",) 阶段性(session)  -->

<include name="path/to/file.xml" />     <!-- 导入另一个 xml, 覆盖到当前 -->

<!-- copyFile, 当你需要复制 文件 到输出文件夹(target.outputDir)时 -->
  <!-- name: 要复制的文件名,可以是任意文件 -->
  <!-- form: 从哪个目录 -->
  <!-- [toolId]: 可选, 所选择 编译工具的 id 值, 某个 linker 元素的 id 值   -->
  <!-- [allowMissing]: 是否允许缺失, 值为 1|t|true 时为真, 其它为假 -->
<copyFile name="libstdc++-6.dll" from="${MINGW_ROOT}/bin" toolId="exe" allowMissing="true" unless="no_shared_libs" />

<section if="Some"></section>           <!-- 所有条件相同的元素集合, 用于避免给每个元素加上同样的条件检测 -->


<pleaseUpdateHxcppTool version="1" />   <!-- Int类型. 如果 hxcpp.n 小于某个版本,将出错误. 这个值似乎很久没用了 -->

<!-- 文件组,id 为文件组标识符, 用于被其它元素引用, 同 id 的文件组, 后边的将覆盖前边的  -->
<files id="__main__">                                                       
  <compilerflag value="-Iinclude" />    <!-- 文件分组编译参数 -->
  <file name="src/__main__.cpp" />      <!-- 分组内的文件 -->
    <depend name="include/Main.h" />    <!-- 所依剌的头文件,位于 file 元素内部只能有 name 属性  -->
    <depend name="include/other.h" />
  </file>

  <depend name="path/file.h" />         <!-- 依赖另一个文件, 注意 depend 属性要么是 name 或 files -->
  <depend files="id" />                 <!-- 依赖另一个文件组内的所有 depend.has.name 元素  -->

  <!-- TODO:  fxc.exe /nologo /T %profile% %file% /Vn %variable% /Fh %target% -->
  <hlsl name="path/tofile.fx" profile="" variable="" target="" />   

  <options name="Options.txt"/>         <!-- TODO: 不明确,反正都是 Options.txt -->
  <precompiledheader name="" dir="" />  <!-- 预编译头文件,name:文件名, dir:目录  -->

  <include name="path/to/file.xml" />   <!-- 导入另一个 xml, 覆盖到当前 -->
  <section></section>
</files>

<!-- 编译, 将源码编译为中间文件时用到 -->
  <!-- id: 名称标识符 -->
  <!-- exe: 编译器命令名, 如 gcc, cl.exe ... -->
<compiler id="android-gcc" exe="g++">
 <exe name="${EXEPREFIX}-g++" if="" />  <!-- 同上,一般写在这里的会有条件检测, 如果重复将覆盖前边设置 -->
 <flag value="" />                      <!-- 编译器参数,用于所有 .c .cpp .m .mm -->
 <flag value="-Dsome=value" />
 <flag value="-I${HXCPP}/include" />

 <cflag value="" />             <!-- 仅用于 .c 的编译参数 -->
 <cppflag value="" />           <!-- 仅用于 .cpp 或 .c++ 的编译参数 -->
 <objcflag value="" />          <!-- 仅用于 .m(object-c) 的编译参数 -->
 <mmflag value="" />            <!-- 仅用于 .mm(object-c++) 的编译参数 -->
 <pchflag value="" />           <!-- 仅用于 .pch(预编译头文件) 的编译参数 -->

 <objdir value="" />            <!-- 输出文件夹目录, 默认为 obj -->
 <outflag value="" />           <!-- 输出 编译标记, 通常为 -o 或 --output -->
 <pch value="" />               <!-- TODO: 未知 - if(pch == "gcc"){...} -->
 <getversion value="some.exe" /><!-- TODO: 一个输出一些字符到 stderr 的工具命令,输出的字符将通过 md5 计算  -->
 <ext value="" />               <!-- 输出 扩展名, 默认为 .o -->
 <include name="" />            <!-- 导入另一个 xml, 覆盖到当前 -->
 <section unless=""></section>  
</compiler>


<!-- strip 是一个去除编译生成的调试内容工具 -->
  <!-- id: 名称标识符 -->
  <!-- exe: strip 工具命令 -->
<stripper id="strip" exe="strip" unless="nostrip">
  <exe name="strip"/>               <!-- 执行 strip 命令名称 -->
  <exe name="arm-none-linux-gnueabi-strip" if="webos" />
  <exe name="${EXEPREFIX}-strip" if="EXEPREFIX" />
  <exe name="${HXCPP_STRIP}" if="HXCPP_STRIP" />
  <exe name="mipsel-linux-strip" if="gcw0" />

  <flag value="-u" if="macos"/>     <!-- strip 命令参数 -->
  <flag value="-r" if="macos"/>
  <flag value="-x" if="macos"/>
  <flag value="-d" if="linux"/>
</stripper>



<!-- 链接器 -->
   <!-- id: 链接器标识符 -->
   <!-- exe: 链接器命令名称 -->
<linker id="dll" exe="link.exe" if="windows">
  <exe name="" if="" />             <!-- 链接器命令名称 -->
  <flag value="-nologo"/>           <!-- linker 命令行参数 -->

  <lib name="libm.so" if="demo" />  <!-- 依赖的链接库 -->
  <lib name="-llog" if="demo"/>
  <lib name="${dll_import_link}" if="dll_import_link" />    

  <libdir name="obj/lib" />         <!-- 依赖的链接库的文件夹 -->

  <fromfile value="@" />            <!-- 如果为 @,所有.obj 将以清单的形式写入到 all_objs. 默认为 @ -->
                                    <!-- windows 需要这个 @ 符号 -->

  <ext value=".dll" />              <!-- 输出扩展名, .exe,.dll,"",.so -->
  <outflag value="-out:" />         <!-- 输出参数: windows通常是 -out:,而非windows通常为 -o -->

  <prefix value="" />           <!-- 输出文件名前缀 -->  
  <ranlib name="" />            <!-- 命令工具,用于更新静态库的符号索引表,似乎只适用于 gcc 编译器 -->
  <recreate value="1" />        <!-- 是否删除输出重新建立, 留空为 false,否则为 true.  -->
  <expandAr value="" />         <!-- 解开 .a 库文件为多个 .o, 留空为 false,否则为 true.-->
  <section></section>
</linker>

<!-- linker 示例 -->
<linker id="exe" exe="link.exe" unless="winrt"> 
  <fromfile value="@"/>
  <flag value="-nologo"/>
  <flag value="-machine:${MACHINE}"/>
  <flag value="-debug" if="HXCPP_DEBUG_LINK"/>
  <flag value="-subsystem:windows${SUBSYSTEM_VER}" if="SUBSYSTEMWINDOWS" />
  <flag value="-subsystem:console${SUBSYSTEM_VER}" if="SUBSYSTEMCONSOLE" />
  <flag value="-libpath:lib"/>
  <flag value="user32.lib"/>
  <ext value=".exe"/>
  <outflag value="-out:"/>
</linker>

<!-- 预链接器, 似乎根本没使用过这个. 子元素参考 linker -->
<prelinker id="" exe="">        
  <exe name="" />               
  <flag value="" />     
  <outflag value="" />          
  <expandAr value="" />         
  <fromfile value="" />
  <section></section>
</prelinker>

<!-- 目标, 所属的一些元素将覆盖 linker 的设定,所以通过 target 元素来修改或增加 linker 的设定 -->
  <!-- id: 标识符, 可以被其它 target 元素当子模块引用, 如果是主 target,请设值为 "default" -->
  <!-- tool: 只有 "linker"" 这一个值,   -->
  <!-- toolid: id值用于链接器选择, 编译顺序将从 prelinker 到 linker  -->
  <!-- output:  输出文件名,不带扩展名.-->
  <!-- overwrite: 重写, 自已看源码定义 -->
<target id="msvccompat" output="" tool="linker" toolid="${STD_MODULE_LINK}" >
  <target id="" />              <!-- 子模块, 指定 id 值就行了 -->
  <lib name="some.lib" />       <!-- 库名, [将覆盖加到 linker] -->
  <flag value="" />             <!-- 编译参数,[将覆盖加到 linker]  -->
  <depend name="" />            <!-- 文件依赖,将检测这些文件是否发生改动来决定是否重新编译 -->
  <dir name="" />               <!-- (好像没用), 当调用 target.clean 时被将删除的目录 -->
  <outdir name="path/to/" />    <!-- 最终输出文件存放目录 -->
  <builddir name="" />          <!-- 设置编译时的当前目录, 目录必须存在. 之后所有相对目录以这个为基准 -->
  <ext value="" />              <!-- 输出扩展名, 如果指定将覆盖 linker 的设定 --> 
  <files id="__main__" />       <!-- 选择需要编译的文件分组, 不同 id 表示不同分组 -->
  <section></section>
</target>

<!-- 最后建议参考 hxcpp/toolchain/haxe-target.xml -->

3.1 ${var} 支持下边前缀

haxelib:        - 返回haxelib库所在路径.  ${haxelib:hxcpp} => `G:\...` 

# 示例: <flag value='-DHXCPP_API_LEVEL=${removeQuotes:hxcpp_api_level}' />
removeQuotes:   - 去除变量返回值的双引号

dospath:        - ??? 将变量返回值转换成DOS path.

dir:            - ??? 和 dospath 一样, 只是多了检测

3.2 Print Information and Error Handling

<echo value="Can anyone hear me?" />

Echo is only allowed at the base level of the xml file. It's useful for printing out detailed information about what is being compiled.

<error value="This message will self destruct in... NOW." />

You can cause an intentional error to occur in hxcpp with this element. Why would you want to do that? Perhaps you don't want to support certain build conditions so instead you decide to show a useful error message to the person compiling your project.

3.3 Define Variables

<set name="foo" value="bar" /> <!-- define "foo" with the value "bar" -->
<unset name="foo" /> <!-- remove the "foo" define -->

<setenv name="foo" value="bar" /> <!-- define "foo" again and set an environment variable -->

I decided to combine set and setenv because they are easy to get mixed up. First, set and unset are used to assign and remove defined values. These are useful for conditional checks as we will see in a moment. The difference between set and setenv is that the latter not only defines a value but also sets it in the operating system environment variables. Note that all of the attributes shown above are required and hxcpp will fail if you forget them.

<!-- NOTE: this uses the "name" attribute and not "value" like you may expect -->
<path name="/path/to/binaries" /> <!-- append to PATH variable -->

The path element works almost identically to setenv with one major difference. It appends to the PATH variable instead of replacing it.

3.4 Conditional Attributes

Every element in the build.xml file can have conditional attributes applied to it. These will help customize the build for each operating system you may be targeting. There are only a handful so let's take a look at each one.

3.4.1 if

<set name="on_boat" value="yep" />
<!-- only checks for the definition of a variable not the actual value -->
<echo value="I'm on a boat" if="on_boat" />
<!-- multiple conditions can be chained together with or statements "||" -->
<echo value="Apple fanboys forever!" if="macos || ios" />
<!-- separating values by spaces means that both must be defined to pass the condition check -->
<echo value="Compiling for a 64-bit linux machine" if="linux HXCPP_M64" />

You can add the if attribute to an element to check when values have been defined.

3.4.2 unless

<set name="have_pants" value="false" />
<!-- this will not execute because the condition passes, note that the value doesn't matter -->
<echo value="I can't find my pants" unless="have_pants" />
<!-- multiple conditions can be chained together with or statements "||" -->
<echo value="We don't like Apple fanboys!" unless="macos || ios" />

unless is the "everything but" condition. Useful for when you want to exclude certain portions of your build configuration.

3.4.3 IfExists

<echo value="Phew, it exists." ifExists="path/to/file.txt" />

This is a special condition for checking if a file exists in the file system.

3.5 Grouping Configurations and Including Other Files

<section if="windows">
    <echo "I'm in another section!" />
</section>
<!-- You can identify sections with an id attribute. This is useful for including in other xml files -->
<section id="my-section"></section>

You can use the section element to define a group of elements. It can have conditions like all the other elements so it's a good way to skip large portions of the build file.

<include name="include/more.xml" />
<!-- Adding the noerror attribute will prevent an error if the include file doesn't exist -->
<include name="does_not_exist.xml" noerror="true" />
<!-- The section attribute lets you restrict the include to a specific section. You must have a coresponding id in the included file. -->
<include name="other.xml" section="my-section" />

Including other configuration files is a good way to split up your build process as it grows larger. You may also want to split out platform specific configuration details into another file as well.

3.6 Miscellaneous Top Level Elements

Before we get to defining targets I want to cover the rest of the other top level elements.

<copyFile name="README.md" from="." />
<!-- Normally copyFile will fail if a file doesn't exist. "allowMissing" prevents that error. -->
<copyFile name="graphics/logo.png" from="assets" allowMissing="true" />
<!-- Adding "toolId" only runs copy file on targets where the toolId attributes match -->
<copyFile name="logo.png" from="assets/graphics" toolId="dll" />

Contrary to what you may think this doesn't copy files immediately, it only happens when a target executes. The name attribute should be a filename and the from attribute is the directory where a file of that name exists. The file will be placed in the target's output directory with the same filename. If you include a directory in the name attribute it has the potential to fail because hxcpp does not create folders for you.

<!-- allowed name values (androidNdk, blackberry, msvc, mingw, emscripten) -->
<setup name="androidNdk" />

The setup element is used to set defines for specific build environments. It takes a name for a build environment and then passes the currently defined values to that tool.

<pleaseUpdateHxcppTool version="0.1" />

Used by hxcpp when the version is outdated. I would advise against using this element unless you know what you're doing.

3.7 Targets and File Groups

Targets are the bread and butter of hxcpp's configuration files. Think of a target as a group of executed commands to eventually either build an executable or some other form of output. Let's go back to the basic example from the beginning of this post for a second.

<xml>
    <target id="default"></target>
</xml>

So we can see that a target at the most basic sense only requires an id attribute. It's also worth noting that the "default" target is important because it specifies the starting point for hxcpp. Without a default target hxcpp will display an error message. Let's add a bit to this example.

  • Hello.cpp
#include <iostream>
int main(int argc, char *argv[])
{
    std::cout << "Hello world!" << std::endl;
    return 0;
}
  • Build.xml
<xml>
    <!-- This is a file group, it contains a set of source files -->
    <files id="common">
        <file name="Hello.cpp" />
    </files>
    <target id="default" output="hello" tool="linker" toolid="exe">
        <files id="common" /> <!-- reference the file group we just created -->
    </target>
</xml>

If you create a Hello.cpp file with the code above and run haxelib run hxcpp Build.xml you'll see that it generates an executable file. Run that executable and you will see Hello world! in the console window. You just compiled a C++ program using hxcpp!

Let's dissect this a bit. First there is a file group, which in this case is just our main C++ file. Following that is our default target but it has a bunch more attributes added to it. The output attribute determines what the final output will be named. If you are on Windows you may have noticed that it automatically adds a ".exe" extension. The tool and toolid attributes work together to determine how hxcpp will compile the code. The only supported value for tool is linker right now and the toolid can be a variety of values (exe, dll, static_link are the most common). You can make your own custom linker tool but I'm not going to cover that in this post.

3.7.1 Compiler Flags

If you've compiled anything beyond the most basic programs in C++ you've probably hit the need to include libraries in your program. Thankfully hxcpp supports external libraries as we'll see below.

<target id="opengl">
    <files id="opengl" />
    <!-- compiler flags -->
    <flag value="-I/usr/local/include" />
    <lib name="-lgl" unless="macos" />
    <vflag name="-framework" value="OpenGL" if="macos" />
</target>

These flags can get a bit confusing because they all basically do the same thing. The main difference between the flag elements and lib is that the former comes before the objects passed to the compiler and the latter is placed after the objects.

Also, flag and vflag are nearly identical except that vflag takes two values and merges them together with a space. It's important to note that you should not add spaces in these values unless you want them to be wrapped in quotes. Which also means that sometimes you have to use workarounds like add multiple lib elements for flags that require spaces.

3.7.2 Directories

<target id="directories">
    <outdir name="build" /> <!-- directory to place final output -->
    <builddir name="source" /> <!-- directory where source files are -->
    <dir name="obj" /> <!-- mentioned in the hxcpp source but never used... -->
</target>

As you can see there are several directory options that can be set. The first is the outdir element that specifies where to put the final output, from the linker step. It should be noted that the outdir name has a forward slash, "/", appended to it. The builddir can be thought of as the base directory. It is where you will find the files you want to compile and it will set the base for the outdir as well.

3.7.3 Dependencies

<target id="misc">
    <depend name="parent" /> <!-- requires another target to finish before this one -->
    <section if="linux"> <!-- works just like the root section -->
        <ext value=".so" /> <!-- add an extension to the output -->
    </section>
</target>

Like other parts of the build configuration, targets can have sections as well. This will group target configuration options just like you could group options at the root level. I've also added the depend element which tells the build tool that it needs to finish a different target before running the current one. Note that this does not use the id attribute but instead uses name.

3.8 Using With Haxe

Up until this point we've been using hxcpp to compile a C++ file directly. This is a perfectly viable way to build C++ and could be used in place of makefiles. However, you are probably wondering how this ties in with the Haxe language. So let's take a look at an example.

  • Main.hx
@:buildXml('<echo value="I added something to Build.xml!" />
<target id="haxe">
    <lib name="-lgl" />
</target>')
class Main
{
    public static function main() { }
}
  • build.hxml
-cpp out
-main Main

The buildXml metadata allows you to insert additional elements at the bottom of the generated Build.xml file. Take a look at out/Build.xml and scroll to the bottom of the file and you will see what was added. When you compile you should see the echo line show up in the output.

Now you may be wondering why there is a target with the id of "haxe". This is a special target id that Haxe creates when transpiling to C++. One thing that hasn't been mentioned so far is that targets can be appended to if they have the same id value.

3.9 Appending and Overriding Targets

If two targets have the same id value they will be appended by default. You can change this by setting an override attribute. See the example below for clarification on how these attributes work.

<xml>
    <target id="foo"></target>
    <target id="foo">
        <!-- append to foo -->
    </target>
    <target id="foo" overwrite="true">
        <!-- remove foo's contents and replace them -->
    </target>
    <target id="foo" append="true">
        <!-- append to foo (same as not having the attribute) -->
    </target>
</xml>

You may want to append or override targets when including other build configuration files. This gives you a lot of flexibility in how you compile your project.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注