# CMake 项目生成


使用 scc 命令生成 CMake 项目

# 概述

CMake 是跨平台的自动化构建系统,它使用简单的配置文件 (CMakeLists.txt) 生成可被本地构建系统构建的项目。scc 支持通过 Julia 源代码生成 CMake 项目,这使得用户可以根据需求修改 CMake 项目中的配置,生成更加定制化的编译产物。同时,生成的 CMake 项目还支持生成跨平台的动态库、静态库和可执行文件。

提示

关于 CMake 的更多信息,请参见 CMake 官方文档 (opens new window)

# scc 生成 CMake 项目示例

scc 的编译选项中指定 --cmake 即可启用 CMake 来构建项目,scc 将在当前目录的 .syslabcc-cache 文件夹下生成 CMake 项目,用户可以通过 -d 选项指定 CMake 项目的输出目录。

提示

使用 scc 的 CMake 项目生成不要求用户环境中已经安装 CMake,因为 Syslab 的 Julia 环境中已预装 CMake_jll 包,它包含一个可用的、经过验证的 CMake。

# 生成可执行文件的 CMake 项目

此例演示通过 scc 生成构建产物为可执行文件的 CMake 项目,我们首先在当前工作目录下新建 main.jl 文件,源码如下所示:

# main.jl

struct Point2D
    x :: Float64
    y :: Float64
end

Base.:+(p1::Point2D, p2:: Point2D) = Point2D(p1.x + p2.x, p1.y + p2.y)

function main()
    p1 = Point2D(1,1)
    p2 = Point2D(3,2)
    p3 = p1 + p2
    display(p3)
end

接下来在终端中输入以下命令:

scc main.jl -o main.exe --cmake -d main_cmake

# linux执行以下命名
# scc main.jl -o main --cmake -d main_cmake --no-blas

此时 scc 将在当前工作目录下生成名为 main_cmake 的目录和基于该项目构建的编译产物,其中,main_cmake目录存放 scc 生成的 CMake 项目。此时文件结构如下所示:

├── bdwgc.dll
├── lib/
├── libmain_cmake.dll.a
├── main.exe
├── main.jl
├── main_cmake/
│   ├── atomic_ops/
│   ├── bdwgc/
│   ├── bin/
│   ├── build/
│   ├── cmakeconfig/
│   ├── ghc-filesystem/
│   ├── lib/
│   ├── syslabcrt-dylib/
│   ├── syslabcrt-intrinsics/
│   ├── syslabcrt-io/
│   ├── win32-implib/
│   ├── CMakeLists.txt
│   ├── juliacmake.jl
│   ├── juliamk.jl
│   ├── src/
│   │   ├── main_cmake.cpp
│   ├── make.jl
│   └── Makefile
└── main_cmake.pdb

Linux 平台下将生成的文件结构如下:

├── bdwgc.so
├── lib/
├── main
├── main.jl
├── main_cmake/
    ├── atomic_ops/
    ├── bdwgc/
    ├── bin/
    ├── build/
    ├── cmakeconfig/
    ├── CMakeLists.txt
    ├── ghc-filesystem/
    ├── juliacmake.jl
    ├── juliamk.jl
    ├── lib/
    ├── src/
    │   ├── main_cmake.cpp
    ├── make.jl
    ├── Makefile
    ├── syslabcrt-dylib/
    ├── syslabcrt-intrinsics/
    ├── syslabcrt-io/
    └── win32-implib/

提示

默认情况下,scc 会自动基于当前平台可用的开发工具链构建生成的 CMake 项目并得到相应的构建产物。如果希望阻止后续的构建过程,可以添加 -c 选项,这将使 scc 仅生成 CMake 项目而不进行后续构建。

项目构建依赖于当前平台的开发工具链,因此在某些平台上,如果项目路径包含中文字符,可能导致终端日志输出出现乱码。为确保日志输出可读,建议将项目生成到不含中文字符的目录中。

在上述文件目录结构中,顶层目录中的 main.jlmain.exe 分别为源码和可执行文件,bdwgc.dll 为代码生成运行时提供垃圾回收功能,其他项为构建过程的中间产物。

main_cmake 目录下是完整的 CMake 项目:

  • bin 是编译目标和依赖动态库的默认输出目录,此例中的编译目标 main.exebdwgc.dll 会生成在该目录下;
  • bin 目录下的 lib 子目录存放源码所依赖的动态库文件,当用户使用 --bundle 选项时 scc 会自动收集这些依赖项;
  • build 为 CMake 的本地构建系统目录,由 CMake 根据 CMakeLists.txt 文件的内容自动生成,CMakeLists.txt 文件描述了整个项目的构建顺序和方式;
  • cmakeconfig 目录下为 CMake 的依赖配置文件,例如编译器和交叉编译的选择配置;
  • CMakeLists.txt 文件为 CMake 项目的入口配置文件,CMake 工具读取其中代码来完成目标构建。

构建完成后,运行程序可得到如下结果:

.\main.exe
# linux执行
# ./main
Point2D(4, 3)

# 生成动态库的 CMake 项目

本例演示如何使用 scc 来生成目标为动态库的 CMake 项目,我们以加法运算的动态库为例,首先在当前工作目录下新建 build.jl 文件,内容如下所示:

# build.jl
add_num(x, y) = x + y

# 使用 SyslabCC 时导出定义函数
@static if @isdefined(SyslabCC)
    # 生成动态库, 导出 C 函数 add_num_i64 让 C++ 调用
    # 其中,(Int, Int) 代表函数输入参数类型
    SyslabCC.static_compile(
        "add_num_i64", add_num, (Int64, Int64));

    # C 函数名可以自定义,此处使用 add_num_f64,表示双精度浮点数加法
    SyslabCC.static_compile(
        "add_num_f64", add_num, (Float64, Float64));
end

此文件经 scc 代码生成后的动态库会导出两个函数,add_num_i64add_num_f64 分别处理 64 位有符号整数和浮点数的加法运算。我们以 Windows 平台为例,在调用 scc 时指定 --mode shared 选项来生成动态库。打开终端并输入如下命令:

scc build.jl --mode shared -d add -o libadd.dll --no-blas --experimental-gen-header --cmake
# linux系统命令为
# scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake

Windows 平台上,执行上述命令后生成产物的目录结构如下所示:

.
├── add/
│   ├── atomic_ops/
│   ├── bdwgc/
│   ├── bin/
│   ├── build/
│   ├── cmakeconfig/
│   ├── ghc-filesystem/
│   ├── lib/
│   ├── syslabcrt-dylib/
│   ├── syslabcrt-intrinsics/
│   ├── syslabcrt-io/
│   ├── win32-implib/
│   ├── src/
│   │   ├── add.cpp
│   ├── add.h
│   ├── CMakeLists.txt
│   ├── juliacmake.jl
│   ├── juliamk.jl
│   ├── make.jl
│   └── Makefile
├── add.h
├── bdwgc.dll
├── build.jl
├── lib/
├── libadd.dll
├── libadd.dll.a

在上述目录结构中:

  • add 文件夹中存放完整的 CMake 项目;
  • bdwgc.dll 为运行时提供垃圾回收功能;
  • libadd.dll 是最终生成的动态库;
  • libadd.dll.a 为动态库的导出库,导出库中有动态库导出的符号,主要用于链接过程;
  • 如果指定了 --experimental-gen-header 选项, scc 会生成 动态库对应的头文件 add.h 供其他源文件引入。

接下来,我们在工作目录下创建一个名为 test_add_num.cpp 的源文件,引入生成的头文件 add.h 并调用动态库 libadd.dll 的中的导出函数,文件内容如下所示:

// test_add_num.cpp

#include <iostream>
#include <stdint.h>
#include "add.h"

int main()
{
    double af = 1.2;
    double bf = 2.4;
    std::cout << "add_num_f64(a = 1.2, b = 2.4) = " << add_num_f64(af, bf) << std::endl;

    int64_t al = 2;
    int64_t bl = 4;
    std::cout << "add_num_i64(a = 2, b = 4) = " << add_num_i64(al, bl) << std::endl;

    return 0;
}

随后在 Syslab 终端中使用 g++ 编译链接上述文件,即可得到可执行文件 main.exe

g++ test_add_num.cpp -L. -ladd -o main.exe

# linux下执行
# g++ test_add_num.cpp -L. -ladd -o main -Wl,-rpath=.

最后保证 bdwgc.dlllibadd.dllmain.exe 都处于同一目录,终端中运行可执行文件得到结果:

.\main.exe

# linux下执行
./main
add_num_f64(a = 1.2, b = 2.4) = 3.6
add_num_i64(a = 2, b = 4) = 6

# 生成静态库的 CMake 项目

scc 支持生成静态库的 CMake 项目,静态库在编译时将库代码直接嵌入到目标可执行文件中,从而生成的可执行文件可以不依赖外部库独立运行。此处我们仍然以加法运算为例,演示静态库的生成。在 Syslab 当前工作目录下新建 build.jl 文件:

# build.jl
add_num(x, y) = x + y

# 使用 SyslabCC 时导出定义函数
@static if @isdefined(SyslabCC)
    # 生成动态库, 导出 C 函数 add_num_i64 让 C++ 调用
    # 其中,(Int, Int) 代表函数输入参数类型
    SyslabCC.static_compile(
        "add_num_i64", add_num, (Int64, Int64));

    # C 函数名可以自定义,此处使用 add_num_f64,表示双精度浮点数加法
    SyslabCC.static_compile(
        "add_num_f64", add_num, (Float64, Float64));
end

在 Syslab 终端使用 scc 生成加法运算的静态库 libadd.a

scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake

# linux下执行
# scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake

提示

  1. 目前静态库生成只在 CMake 项目中被支持,因此需要我们同时使用选项 --mode static--cmake
  2. 尽管同样的 julia 源码可以生成动态库或静态库,并且都能使用 --experimental-gen-header 选项自动生成头文件,但由于动态库和静态库生成的头文件内容不同,二者无法混用。

然后,我们同样在工作目录下创建一个名为 test_add_num.cpp 的源文件,引入静态库的头文件 add.h 然后调用静态库的 libadd.a 的函数:

// test_add_num.cpp

#include <iostream>
#include <stdint.h>
#include "add.h"

int main()
{
    double af = 1.2;
    double bf = 2.4;
    std::cout << "add_num_f64(a = 1.2, b = 2.4) = " << add_num_f64(af, bf) << std::endl;

    int64_t al = 2;
    int64_t bl = 4;
    std::cout << "add_num_i64(a = 2, b = 4) = " << add_num_i64(al, bl) << std::endl;

    return 0;
}

最后,在终端中使用 zig 编译链接源文件和静态库得到可执行文件,运行可执行文件得到结果(Syslab 已预装 zig c++ 工具):

# win10及以上执行:
zig c++ test_add_num.cpp -L. -ladd -o main.exe
.\main.exe

# win7执行:
# g++ test_add_num.cpp -o main.exe -L. -ladd
# .\main.exe

# linux执行:
# zig c++ test_add_num.cpp -L. -ladd -o main
# ./main
add_num_f64(a = 1.2, b = 2.4) = 3.6
add_num_i64(a = 2, b = 4) = 6

# 基于 CMake 交叉编译

基于 CMake 项目生成,scc 可以将 julia 源码交叉编译到不同目标平台的可执行文件、动态库或静态库。

提示

Windows7 暂不支持跨平台交叉编译。

以下示例中的 main.jlbuild.jltest_add_num.cpp文件内容如下所示:

# main.jl

struct Point2D
    x :: Float64
    y :: Float64
end

Base.:+(p1::Point2D, p2:: Point2D) = Point2D(p1.x + p2.x, p1.y + p2.y)

function main()
    p1 = Point2D(1,1)
    p2 = Point2D(3,2)
    p3 = p1 + p2
    display(p3)
end
# build.jl
add_num(x, y) = x + y

# 使用 SyslabCC 时导出定义函数
@static if @isdefined(SyslabCC)
    # 生成动态库, 导出 C 函数 add_num_i64 让 C++ 调用
    # 其中,(Int, Int) 代表函数输入参数类型
    SyslabCC.static_compile(
        "add_num_i64", add_num, (Int64, Int64));

    # C 函数名可以自定义,此处使用 add_num_f64,表示双精度浮点数加法
    SyslabCC.static_compile(
        "add_num_f64", add_num, (Float64, Float64));
end
// test_add_num.cpp

#include <iostream>
#include <stdint.h>
#include "add.h"

int main()
{
    double af = 1.2;
    double bf = 2.4;
    std::cout << "add_num_f64(a = 1.2, b = 2.4) = " << add_num_f64(af, bf) << std::endl;

    int64_t al = 2;
    int64_t bl = 4;
    std::cout << "add_num_i64(a = 2, b = 4) = " << add_num_i64(al, bl) << std::endl;

    return 0;
}

接下来我们将展示在主机 CPU 架构为 x86_64 的 Windows 和 Linux 平台下的交叉编译命令。

# 当前主机为 x86_64 的 Windows 平台

本节将列出在当前主机为 x86_64 的 Windows 平台编译至不同目标平台的编译命令,如果需要编译至自身平台,则可以省略 --os--arch 选项。

  • 生成可执行文件

    目标平台 指令
    aarch64-linux-gnu scc main.jl -o main -d arm64main --cmake --no-blas --os linux --arch arm64
    x86_64-linux-gnu scc main.jl -o main -d x86_64main --cmake --no-blas --os linux --arch x86_64
    x86-linux-gnu scc main.jl -o main -d x86main --cmake --no-blas --os linux --arch x86
    x86-windows-gnu scc main.jl -o main -d x86main --cmake --no-blas --os windows --arch x86
  • 生成动态库

    目标平台 指令
    aarch64-linux-gnu scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake --os linux --arch arm64
    zig c++ --target=aarch64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main "-Wl,-rpath=."
    x86_64-linux-gnu scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake --os linux --arch x86_64
    zig c++ --target=x86_64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main "-Wl,-rpath=."
    x86-linux-gnu scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake --os linux --arch x86
    zig c++ --target=x86-linux-gnu test_add_num.cpp -L. -ladd -o main "-Wl,-rpath=."
    x86-windows-gnu scc build.jl --mode shared -d add -o libadd.dll --no-blas --experimental-gen-header --cmake --os windows --arch x86
    zig c++ --target=x86-windows-gnu test_add_num.cpp -o test.exe -L"./" libadd.dll "-Wl,-rpath=."
  • 生成静态库

    目标平台 指令
    aarch64-linux-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os linux --arch arm64
    zig c++ --target=aarch64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main
    x86_64-linux-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os linux --arch x86_64
    zig c++ --target=x86_64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main
    x86-linux-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os linux --arch x86
    zig c++ --target=x86-linux-gnu test_add_num.cpp -L. -ladd -o main
    x86-windows-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os windows --arch x86
    zig c++ --target=x86-windows-gnu test_add_num.cpp -L. -ladd -o main.exe

# 当前主机为 x86_64 的 Linux 平台

本节将列出在当前主机为 x86_64 的 Linux 平台编译至不同目标平台的编译命令,如果需要编译至自身平台,则可以省略 --os--arch 选项。

  • 生成可执行文件

    目标平台 指令
    aarch64-linux-gnu scc main.jl -o main -d arm64main --cmake --no-blas --os linux --arch arm64
    x86_64-windows-gnu scc main.jl -o main -d x86_64main --cmake --no-blas --os windows --arch x86_64
    x86-linux-gnu scc main.jl -o main -d x86main --cmake --no-blas --os linux --arch x86
    x86-windows-gnu scc main.jl -o main -d x86main --cmake --no-blas --os windows --arch x86
  • 生成动态库

    目标平台 指令
    aarch64-linux-gnu scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake --os linux --arch arm64
    zig c++ --target=aarch64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main
    x86_64-windows-gnu scc build.jl --mode shared -d add -o libadd.dll --no-blas --experimental-gen-header --cmake --os windows --arch x86_64
    zig c++ --target=x86_64-windows-gnu test_add_num.cpp -L. -ladd.dll -o main.exe
    x86-linux-gnu scc build.jl --mode shared -d add -o libadd.so --no-blas --experimental-gen-header --cmake --os linux --arch x86
    zig c++ --target=x86-linux-gnu test_add_num.cpp -L. -ladd -o main "-Wl,-rpath=."
    x86-windows-gnu scc build.jl --mode shared -d add -o libadd.dll --no-blas --experimental-gen-header --cmake --os windows --arch x86
    zig c++ --target=x86-windows-gnu test_add_num.cpp -L. -ladd.dll -o main.exe "-Wl,-rpath=."
  • 生成静态库

    目标平台 指令
    aarch64-linux-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os linux --arch arm64
    zig c++ --target=aarch64-linux-gnu.2.17 test_add_num.cpp -L. -ladd -o main
    x86_64-windows-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os windows --arch x86_64
    zig c++ --target=x86_64-windows-gnu test_add_num.cpp -L. -ladd -o main.exe
    x86-linux-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os linux --arch x86
    zig c++ --target=x86-linux-gnu test_add_num.cpp -L. -ladd -o main
    x86-windows-gnu scc build.jl --mode static -d add -o libadd.a --no-blas --experimental-gen-header --cmake --os windows --arch x86
    zig c++ --target=x86-windows-gnu test_add_num.cpp -L. -ladd -o main.exe

# 注意事项

使用 scc 命令生成 CMake 项目时,有以下几点需要注意:

  • CMake 在构建过程中会优先使用缓存,这种策略虽然可以加快构建过程,但是当缓存内容不一致时会无法成功构建。因此如果在已经存在 CMake 项目的目录下多次使用 scc 命令,需要保证每次生成的CMake项目是兼容的,例如上一次生成的项目使用 --mode shared --cmake 选项生成了动态库,这次希望在相同目录下使用 --mode static --cmake 生成静态库项目,就需要先清除前一次生成的 CMake 项目,再使用 scc生成新项目。当没有使用 -d选项时,CMake 项目会生成在 .syslabcc-cache 目录中,多次使用 scc 生成不同类型的 CMake 项目时,也需要先清理此目录中的内容;
  • CMake 是一个跨平台的自动化构建系统,这意味着可以将 scc 生成的 CMake 项目复制到任意支持的平台主机上本地构建,用户可以按需修改 CMakeLists.txt 文件的内容;
  • 如果需要将 CMake 项目从 Windows 平台移动到 Linux(或反向移动)进行本地构建,还需要满足 Julia 源码中没有使用 blas 线性代数库这个条件,并在生成 CMake 项目时使用 --no-blas 选项;
  • 跨平台本地构建时,scc--bundle 选项生成的依赖库将会失效,因为这些依赖库都是平台相关的。