Conditional Compilation
Developers can achieve conditional compilation through predefined or custom conditions. Currently, Cangjie supports conditional compilation for imports and declarations.
Conditional Compilation for Imports and Declarations
Cangjie supports using the built-in compilation marker @When for conditional compilation. Compilation conditions are enclosed in [], which can contain one or multiple sets of conditions. @When can be applied to import nodes and declaration nodes (except package).
Usage Example
Taking the built-in os compilation condition as an example, its usage is as follows:
In the above code, developers can successfully compile and execute it in Linux systems; in non-Linux systems, they will encounter a compilation error indicating that the mc class definition cannot be found.
Important notes:
- Cangjie does not support nested conditional compilation. The following syntax is prohibited:
- @When[...] is a built-in compilation marker processed before imports. If code generated by macro expansion contains @When[...], it will result in a compilation error, such as:
@When[os == "Linux"]
class mc{}
main(): Int64 {
var a = mc()
return 0
}Built-in Conditional Variables
Cangjie provides the following built-in conditional variables: os, arch, env, backend, cjc_version, debug and test.
os
os represents the target platform's operating system. It supports == and != operators. Supported operating systems include: Windows, Linux, macOS, iOS.Usage example:
When compiled and executed in a Windows environment, it will output Windows, NOT Linux; in a Linux environment, it will output Linux, NOT Windows.
@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()
}arch
arch represents the target platform's processor architecture. It supports == and != operators.Supported architectures: x86_64, aarch64.
Usage example:
When compiled and executed on an x86_64 architecture platform, it will output x86_64; on an aarch64 architecture platform, it will output aarch64.
@When[arch == "aarch64"]
var arch = "aarch64"
@When[arch == "x86_64"]
var arch = "x86_64"
main() {
println(arch)
}env
env provides additional information, such as the ABI (Application Binary Interface) of the target platform, to eliminate ambiguities between different target platforms. It supports == and != operators.Supported environment: ohos,gnu,simulator,android and default(empty string).
Usage example:
When compiled and executed on the OpenHarmony target platform, you will get information ohos; when compile on other target platform, you will get information other
@When[env == "ohos"]
var env = "ohos"
@When[env != "ohos"]
var env = "other"
main() {
println(env)
}backend
backend represents the target platform's backend type, supporting conditional compilation for multiple backends. It supports == and != operators.Currently supported backends: cjnative.
Usage example:
When compiled and executed with the cjnative backend package, it will output cjnative backend.
@When[backend == "cjnative"]
func foo() {
print("cjnative backend")
}
@When[backend != "cjnative"]
func foo() {
print("not cjnative backend")
}
main() {
foo()
}cjc_version
cjc_version is a built-in condition that allows developers to select code based on the current Cangjie compiler version. It supports ==, !=, >, <, >=, and <= operators. The format is xx.xx.xx, where each xx supports 1-2 digits. The comparison rule pads each part to 2 digits, e.g., 0.18.8 < 0.18.11, 0.18.8 == 0.18.08.Usage example:
The output of the above code will vary depending on the cjc version.
@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()
}debug
debug indicates whether debug mode is enabled (i.e., the -g compilation option is used). It can be used to switch between debug and release builds. It only supports the logical NOT operator (!).Usage example:
When compiled with -g, it will output debug; without -g, it will output NOT debug.
@When[debug]
func foo() {
println("debug")
}
@When[!debug]
func foo() {
println("NOT debug")
}
main() {
foo()
}test
test indicates whether the unit test option --test is enabled. It only supports the logical NOT operator (!). It can be used to distinguish test code from regular code.Usage example:
When compiled with --test, it will produce test results; without --test, it will compile and run normally, outputting run.
@When[test]
@Test
class Tests {
@TestCase
public func case1(): Unit {
@Expect("run", foo())
}
}
func foo() {
"run"
}
@When[!test]
main () {
println(foo())
}Custom Conditional Variables
Cangjie allows developers to define custom conditional variables and values. Custom variable names must be valid identifiers and cannot conflict with built-in variables. Their values are string literals. Custom conditions support == and != operators. Unlike built-in variables, custom conditions must be defined via the --cfg compilation option or in the cfg.toml configuration file.
Configuring Custom Conditional Variables
There are two ways to configure custom conditional variables: directly via compilation options or through a configuration file.
Developers can use --cfg to pass custom compilation conditions as key-value pairs or specify the search path for the cfg.toml configuration file.
- Option values must be enclosed in double quotes.
- If the option value contains =, it will be treated as a key-value pair (if the path contains =, it must be escaped with \). Multiple key-value pairs can be separated by commas ,. For example:
- Multiple --cfg options can be used, e.g.:
- Defining the same variable multiple times is prohibited, e.g.:
Both commands will result in errors.
- If the option value does not contain = or contains an escaped =, it will be treated as the search path for cfg.toml. For example:
If ./cfg/cfg.toml exists, the compiler will automatically read the custom conditions defined in it. The cfg.toml file should contain key-value pairs, with each pair on a separate line. Keys must be valid Cangjie identifiers, and values must be double-quoted strings (no escape sequences). Full-line and inline comments are supported, e.g.:
- When multiple --cfg options specify cfg.toml search paths, they are searched in the order provided. If no cfg.toml is found in any path, the compiler will search for cfg.toml in the default path.
- If any --cfg option directly provides key-value pairs, the cfg.toml configuration will be ignored.
- If no --cfg option is used, the compiler will search for cfg.toml in the default path (the package directory specified by --package or -p, or the cjc execution directory).
Multi-Conditional Compilation
Cangjie allows developers to combine multiple conditional compilation options freely. Logical operators and parentheses can be used to specify precedence.
Usage example 1:
Compile and run the above code using the following command:
The output will be as follows:
Usage example 2:
Cangjie cross-compiled to the target platform aarch64-linux-android31. The conditional variables are set as shown in the code below. If you need to cross-compile to other platforms, please refer to the Target platform and Conditional Compilation Mapping Table .
//source.cj
@When[(test || feature == "lion") && !debug]
func fee() {
println("feature lion")
}
main() {
fee()$ cjc --cfg="feature=lion" source.cj -o runner.outfeature lion@When[os == "Linux" && arch == "aarch64" && env == "android"]
func foo() {
"target aarch64-linux-android31 run"
}
main() {
println(foo())
}Appendix
Target platform and Conditional Compilation Mapping Table
The target platforms supported by Cangjie's cross-compilation are determined by the build-in conditional variables os, arch and env. The mapping between this three variables and the target platforms is shown in the table below:
| target platform | arch | os | env |
| ----------------------------- | --------- | --------- | ----------- |
| x86_64-windows-gnu | "x86_64" | "Windows" | "gnu" |
| x86_64-linux-gnu | "x86_64" | "Linux" | "gnu" |
| x86_64-apple-darwin | "x86_64" | "macOS" | "" |
| x86_64-linux-ohos | "x86_64" | "Linux" | "ohos" |
| x86_64-w64-mingw32 | "x86_64" | "Windows" | "gnu" |
| x86_64-linux-android[26+][android target] | "x86_64" | "Linux" | "android" |
| aarch64-linux-gnu | "aarch64" | "Linux" | "gnu" |
| aarch64-linux-android[26+][android target] | "aarch64" | "Linux" | "android" |
| aarch64-apple-darwin | "aarch64" | "macOS" | "" |
| aarch64-linux-ohos | "aarch64" | "Linux" | "ohos" |
| arm64-apple-ios[11+][ios target] | "aarch64" | "iOS" | "" |
| arm64-apple-ios[11+]-simulator[ios target] | "aarch64" | "iOS" | "simulator" |
[ios target] arm64-apple-ios[11+], the number following the ios suffix the ios version. If no number is specified, the default ios version is 11; specifying a number(e.g., arm64-apple-ios26)indicates that the ios version is 26,and the number of ios version must greater than or equal to 11.