一、cjc 使用
cjc是仓颉编程语言的编译命令,其提供了丰富的功能及对应的编译选项,本章将对基本使用方法进行介绍。
cjc-frontend (仓颉前端编译器)会随 cjc 一起通过 Cangjie SDK 提供,cjc-frontend 能够将仓颉源码编译至仓颉的中间表示 (LLVM IR)。 cjc-frontend 仅进行仓颉代码的前端编译,虽然 cjc-frontend 和 cjc 共享部分编译选项,但编译流程会在前端编译结束时中止。使用 cjc 时仓颉编译器会自动进行前端、后端的编译以及链接工作。cjc-frontend 仅作为前端编译器的实体体现提供,除编译器开发者外,仓颉代码的编译应优先使用 cjc 。
1.1 cjc 基本使用方法
想必你已经在学习仓颉的过程中尝试着使用 cjc 了,我们先来看一下 cjc 的基本使用方法,如果你想了解详细的编译选项内容,请自行查阅“cjc 编译选项”章节。
cjc 的使用方式如下:
cjc [option] file...
假如我们有一个名为 hello.cj 的仓颉文件:
main() { println("Hello, World!") }
我们可以使用以下命令来编译此文件:
$ cjc hello.cj
此时工作目录下会新增可执行文件 main ,cjc 默认会将给定源代码文件编译成可执行文件,并将可执行文件命名为 main。
以上为不给任何编译选项时 cjc 的默认行为,我们可以通过使用编译选项来控制 cjc 的行为,例如让 cjc 进行整包编译,又或者是指定输出文件的名字。
二、cjpm 介绍
CJPM(Cangjie Package Manager) 是仓颉语言的官方包管理工具,用来管理、维护仓颉项目的模块系统,并且提供更简易统一的编译入口,支持自定义编译命令。通过包管理器自动依赖管理实现对引入的多版本三方依赖软件进行分析合并,无需开发者担心多版本依赖冲突问题,大大减轻开发者负担;同时提供基于仓颉语言原生的自定义构建机制,允许开发者在构建的不同阶段增加预处理和后处理流程,实现构建流程可灵活定制,能够满足开发者不同业务场景下的编译构建诉求。
2.1 cjpm 基本使用方法
通过 cjpm -h 即可查看主界面,由几个板块组成,从上到下分别是: 当前命令说明、使用示例(Usage)、支持的可用命令(Available subcommands)、支持的配置项(Available options)、更多提示内容。
Cangjie Package Manager Usage: cjpm [subcommand] [option] Available subcommands: init Init a new cangjie module check Check the dependencies update Update cjpm.lock tree Display the package dependencies in the source code build Compile the current module run Compile and run an executable product test Unittest a local package or module clean Clean up the target directory publish Push a module to the repository load Load a module from the repository list Get module list from the repository install Install a cangjie binary uninstall Uninstall a cangjie binary Available options: -h, --help help for cjpm -v, --version version for cjpm Use "cjpm [subcommand] --help" for more information about a command.
cjpm init 用来初始化一个新的仓颉模块或者工作空间。初始化模块时会默认在当前文件夹创建 cjpm.toml 文件,并且新建 src 源码文件夹,在 src 下生成默认的 main.cj 文件。自定义参数初始化功能支持可以通过cjpm init -h 查看。
例如:
输入: cjpm init 输出: cjpm init success
cjpm build 用来构建当前仓颉项目,执行该命令前会先检查依赖项,检查通过后调用 cjc 进行构建。支持全量编译、增量编译、交叉编译、并行编译等,更多编译功能支持可以通过cjpm build -h 查看。通过cjpm build -V 命令可以打印所有的编译过程命令。
例如:
输入: cjpm build -V 输出: compile package module1.package1: cjc --import-path target -p "src/package1" --output-type=staticlib -o target/release/module1/libmodule1.package1.a compile package module1: cjc --import-path target -L target/release/module1 -lmodule1.package1 -p "src" --output-type=exe --output-dir target/release/bin -o main cjpm build success
2.2 cjpm.toml 配置文件说明
配置文件 cjpm.toml 用来配置一些基础信息、依赖项、编译选项等内容,cjpm 主要通过这个文件进行解析执行。
配置文件代码如下所示:
[package] cjc-version = "0.49.1" # 所需 `cjc` 的最低版本要求,必须 name = "demo" # 模块名及模块 root 包名,必须 description = "nothing here" # 描述信息,非必须 version = "1.0.0" # 模块版本信息,必须 compile-option = "" # 额外编译命令选项,非必须 link-option = "" # 链接器透传选项,可透传安全编译命令,非必须 output-type = "executable" # 编译输出产物类型,必须 src-dir = "" # 指定源码存放路径,非必须 target-dir = "" # 指定产物存放路径,非必须 package-configuration = {} # 单包配置选项,非必须 [workspace] # 工作空间管理字段,与 package 字段不能同时存在 members = [] build-members = [] test-members = [] compile-option = "" link-option = "" target-dir = "" [dependencies] # 源码依赖配置项 aoo = { version = "1.0.0" } # 导入中心仓依赖 boo = "1.1.0" # 导入中心仓依赖 coo = { git = "xxx",branch = "dev" , version = "1.0.0"} # 导入 `git` 依赖,`version`字段可缺省 doo = { path = "./pro1" ,version = "1.0.0"} # 导入源码依赖,`version`字段可缺省 [test-dependencies] # 测试阶段的依赖配置项 [ffi.c] # 导入 `c` 库依赖 clib1.path = "xxx" [profile] # 命令剖面配置项 build = {} test = {} customized-option = {} [target.x86_64-unknown-linux-gnu] # 后端和平台隔离配置项 compile-option = "value1" # 额外编译命令选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须 link-option = "value2" # 链接器透传选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须 [target.x86_64-w64-mingw32.dependencies] # 适用于对应 target 的源码依赖配置项,非必须 [target.x86_64-w64-mingw32.test-dependencies] # 适用于对应 target 的测试阶段依赖配置项,非必须 [target.cjvm.bin-dependencies] # 仓颉二进制库依赖,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须 path-option = ["./test/pro0", "./test/pro1"] [target.cjvm.bin-dependencies.package-option] "pro0.xoo" = "./test/pro0/pro0.xoo.cjo" "pro0.yoo" = "./test/pro0/pro0.yoo.cjo" "pro1.zoo" = "./test/pro1/pro1.zoo.cjo"
三、条件编译
开发者可以通过预定义或自定义的条件完成条件编译;仓颉目前支持导入和声明的条件编译。
3.1 导入和声明的条件编译
仓颉支持使用内置编译标记 @When 来完成条件编译,编译条件使用 [] 括起来,[] 内支持输入一组或多组编译条件。@When 可以作用于导入节点和除 package 外的声明节点。
使用方法
以内置 os 编译条件为例,其使用方法如下:
@When[os == "Linux"] class mc{} main(): Int64 { var a = mc() return 0 }
在上面代码中,开发者在 Linux 系统中可以正确编译执行;在 非 Linux 系统中,则会遇到找不到 mc 类定义的编译错误。
值得注意的是:
- 仓颉不支持编译条件嵌套,以下写法均不允许:
@When[os == "Windows"] @When[os == "Linux"] // Error, illegal nested when conditional compilation import std.ast.* @When[os == "Windows"] @When[os == "Linux"] // Error, illegal nested when conditional compilation func A(){}
- @When[…] 作为内置编译标记,在导入前处理,由宏展开生成的代码中含有 @When[…] 会编译报错,如:
@M0 // macro which returns the input @When[os == "Linux"] // Error, unexpected when conditional compilation directive func A(){}
3.2 内置编译条件变量
仓颉提供了五个内置条件变量: os、 backend、 cjc_version、 debug 和 test。
3.2.1 os
os 表示目标平台的操作系统。os 支持 == 和 != 两种操作符。支持的操作系统有:Windows、Linux、macOS、HarmonyOS。
使用方式如下:
@When[os == "Linux"] func foo() { print("Linux, ") } @When[os == "Windows"] func foo() { print("Windows, ") } @When[os != "Windows"] func fee() { println("NOT Windows") } @When[os != "Linux"] func fee() { println("NOT Linux") } main() { foo() fee() }
如果在 Windows 环境下编译执行,会得到 Windows, NOT Linux 的信息;如果是在 Linux 环境下,则会得到 Linux, NOT Windows 的信息。
3.2 .2 backend
backend 是仓颉内置的条件。仓颉是多后端语言,支持多种后端条件编译。backend 条件支持 == 和 != 两种操作符。
支持的后端有:cjnative、cjnative-x86、cjnative-x86_64、cjnative-arm、cjnative-aarch64、cjvm、cjvm-x86、cjvm-x86_64、cjvm-arm、cjvm-aarch64。
当用户使用的条件为 cjnative/cjvm 时,arch 信息将会按编译器执行时环境信息自动补全。
使用方式如下:
@When[backend == "cjnative"] func foo() { print("cjnative backend, ") } @When[backend == "cjvm"] func foo() { print("cjvm backend, ") } @When[backend != "cjnative"] func fee() { println("NOT cjnative backend") } @When[backend != "cjvm"] func fee() { println("NOT cjvm backend") } main() { foo() fee() }
用 cjnative 后端的发布包编译执行,会得到 cjnative backend, NOT cjvm backend 的信息;用 cjvm 后端的发布包编译执行,则会得到 cjvm backend, NOT cjnative backend 的信息。
3.2.3 cjc_version
cjc_version 是仓颉内置的条件,开发者可以根据当前仓颉编译器的版本选择要编译的代码。cjc_version 条件支持 ==、!=、>、<、>=、<= 六种操作符,格式为 xx.xx.xx 支持每个 xx 支持 1-2 位数字,计算规则为补位 (补齐 2 位) 比较,例如:0.18.8 < 0.18.11, 0.18.8 == 0.18.08。
使用方式如下:
@When[cjc_version == "0.18.6"] func foo() { println("cjc_version equals 0.18.6") } @When[cjc_version != "0.18.6"] func foo() { println("cjc_version is NOT equal to 0.18.6") } @When[cjc_version > "0.18.6"] func fnn() { println("cjc_version is greater than 0.18.6") } @When[cjc_version <= "0.18.6"] func fnn() { println("cjc_version is less than or equal to 0.18.6") } @When[cjc_version < "0.18.6"] func fee() { println("cjc_version is less than 0.18.6") } @When[cjc_version >= "0.18.6"] func fee() { println("cjc_version is greater than or equal to 0.18.6") } main() { foo() fnn() fee() }
根据 cjc 的版本,上面代码的执行输出结果会有不同。
3.2.4 debug
debug 表示当前是否启用了调试模式即开启 -g 编译选项, 可以用于在编译代码时进行调试和发布版本之间的切换。debug 条件仅支持逻辑非运算符(!)。
使用方式如下:
@When[debug] func foo() { println("debug") } @When[!debug] func foo() { println("NOT debug") } main() { foo() }
启用 -g 编译执行会得到 cjc debug 的信息,如果没有启用 -g 编译执行会得到 NOT debug 的信息。
3.2.5 test
test 表示当前是否启用了单元测试选项 --test。test 条件仅支持逻辑非运算符(!)。可以用于区分测试代码与普通代码。
使用方式如下:
@When[test] @Test class Tests { @TestCase public func case1(): Unit { @Expect("run", foo()) } } func foo() { "run" } @When[!test] main () { println(foo()) }
使用 --test 编译执行得到的测试结果,不使用 --test 也可正常完成编译运行得到 run 的信息。
3.3 自定义编译条件变量
仓颉允许开发者自定义编译条件变量和取值,自定义的条件变量必须是一个合法的标识符且不允许和内置条件变量同名,其值是一个字符串字面量。自定义条件支持 == 和 != 两种运算符。和内置条件变量不同点在于自定义的条件需要开发者在编译时通过 --cfg 编译选项或者在配置文件 cfg.toml 中定义。
3.3.1 配置自定义条件变量
配置自定义条件变量的方式有两种:在编译选项中直接配置键值对或在配置文件配置键值对。
用户可以使用 --cfg 以键值对的形式向编译器传递自定义编译条件变量或者指定配置文件 cfg.toml 的搜索路径。
选项值需要使用双引号括起来
若选项值中包含 = 则会按照键值对的形式直接进行配置(若路径中包含 = 则需要通过 \ 转义),多个键值对可以使用逗号 , 分隔。如:
$ cjc --cfg "feature = lion, platform = dsp" source.cj
- 允许多次使用 --cfg 编译选项配置进行配置, 如:
$ cjc --cfg "feature = lion" --cfg "platform = dsp" source.cj
- 不允许多次定义同一个条件变量, 如:
$ cjc --cfg "feature = lion" --cfg "feature = meta" source.cj $ cjc --cfg "feature = lion, feature = meta" source.cj
上述两条编译指令都会报错。
- 若选项值中不包含 = 或 存在通过 \ 转义的 = 则将选项值作为配置文件 cfg.toml 的搜索路径传递给编译器,如:
$ cjc --cfg "./cfg" source.cj
若 ./cfg 目录下存在 cfg.toml 则在编译时,编译器会将 ./cfg/cfg.toml 中配置的自定义编译条件传递给编译器。cfg.toml 文件中应采用键值对的方式配置自定义条件变量,每个键值对独占一行, 健名是一个合法的标识符, 键值是一个双引号括起来的字符串。如:
feature = "lion" platform = "dsp"
多次使用 --cfg 配置 cfg.toml 文件的搜索路径时,按照传入的顺序依次搜索cfg.toml 文件,若在所有传入的搜索路径下都没有找到 cfg.toml 文件,则在默认路径下搜索配置文件 cfg.toml。
多次使用 --cfg 编译选项进行配置时,若某次以键值对的形式直接进行配置,则会忽略配置文件 cfg.toml 中的配置。
若没有使用 --cfg 编译选项,编译器会在默认路径(通过–package 或 -p 指定的 package 目录或 cjc 执行目录)下搜索配置文件 cfg.toml。
3.4 多条件编译
仓颉条件编译允许开发者自由组合多个条件编译选项。支持逻辑运算符组合多个条件,支持括号运算符明确优先级。
使用方式如下:
//source.cj @When[(test || feature == "lion") && !debug] func fee() { println("feature lion") } main() { fee() }
使用如下编译命令编译运行上段代码,
$ cjc --cfg="feature=lion" source.cj -o runner.out
会得到输出结果如下:
platform lion