# 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.jl 和 main.exe 分别为源码和可执行文件,bdwgc.dll 为代码生成运行时提供垃圾回收功能,其他项为构建过程的中间产物。
main_cmake 目录下是完整的 CMake 项目:
bin是编译目标和依赖动态库的默认输出目录,此例中的编译目标main.exe和bdwgc.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_i64 和 add_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.dll、libadd.dll、main.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
提示
- 目前静态库生成只在 CMake 项目中被支持,因此需要我们同时使用选项
--mode static和--cmake; - 尽管同样的 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.jl、build.jl 和 test_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 mainx86_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 mainx86-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 mainx86-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 mainx86_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.exex86-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 mainx86_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.exex86-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 mainx86-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选项生成的依赖库将会失效,因为这些依赖库都是平台相关的。