Open davidchisnall opened 1 month ago
Now we see some of the less-nice bits of xmake. You can't extend the xmake declarative syntax, you can only add properties in the imperative world, so we now need to write a Lua function that runs when this target is loaded.
Xmake supports custom description scope interfaces. You can customize the firmware and compartment scope api, as well as all configuration interfaces, like this:
https://github.com/xmake-io/xmake/blob/dev/xmake/includes/xpack/xmake.lua
https://github.com/xmake-io/xmake/issues/1433#issuecomment-1811761871
Targets in xmake must have globally unique names.
v3.0 will provide namespace to support it. https://github.com/xmake-io/xmake/issues/5527
If you add a compiler flag, xmake will drop it if it isn't supported. It doesn't try passing it to the compiler to see if it's supported
you can disable this policy. set_policy("check.auto_ignore_flags", false)
Xmake supports custom description scope interfaces. You can customize the firmware and compartment scope api, as well as all configuration interfaces, like this:
Are these documented? I thought they were only for internal use. If we can use them, we could tidy up a lot of our code.
It would be great if we could support add_threads
and add_preshared_object
on our target types and remove the need for downstream users to write on_load
things in most cases.
v3.0 will provide namespace to support it
Yay!
you can disable this policy.
This comes back to the scoping problems. Yes, I can disable it, but I have to set this policy in every file. I can’t just put it at the top of the SDK file that everyone includes and have it fixed.
And that’s related to the ‘sane defaults’ problem. It is a very bad design choice for xmake to decide that it knows what flags an unknown version of a third-party tool supports. This will always be wrong because xmake has no visibility into what the tool is. As such, it gets added to the list of things where, in every project, I need to change the defaults.
Are these documented? I thought they were only for internal use. If we can use them, we could tidy up a lot of our code.
no document now. But it's public api. you can see above links.
and see https://github.com/xmake-io/xmake/issues/4276
and whole example:
https://github.com/xmake-io/xmake/blob/dev/tests/apis/custom_scopeapis/xmake.lua
Thanks, that should be enough for us to define the library/firmware/compartment targets properly. The two big things that we're missing beyond that are:
Ideally, the xmake file linked above would look like this:
set_project("CHERIoT Hello World")
-- Some mechanism to provide this on the command line.
sdkdir ?= "../../sdk"
includes(sdkdir)
-- No set_toolchains here, imported from sdkdir
option("board")
set_default("sail")
compartment("hello")
add_deps("freestanding", "debug")
add_files("hello.cc")
-- Firmware image for the example.
firmware("hello_world")
add_deps("hello")
set_board("$(board)")
add_thread({
compartment = "hello",
priority = 1,
entry_point = "say_hello",
stack_size = 0x200,
trusted_stack_frames = 1
})
sdkdir = os.getenv("SDK") or "../../sdk"
includes(sdkdir)
SDK=/xxxx/sdk xmake
Firmware images inevitably pull in lots of external code that uses something other than xmake for building. You can see this here:
https://opensecura.googlesource.com/hw/matcha/+/refs/heads/master/sw/device/cheriot/soundstream
where I chose to use xmake because of the complexity of building a firmware image but then had to explicitly collect the OpenTitan dependencies for the SPI support.
The other thing I'd mention is that firmware images must be consumed by a test environment. This means packaging, loading, etc. Here's a simple example of integrating cheriot-rtos firmware image building:
https://opensecura.googlesource.com/build/+/refs/heads/master/platforms/sencha/cheriot.mk
and how we tie things together for simulation with renode & mpact-cheriot:
https://opensecura.googlesource.com/build/+/refs/heads/master/platforms/bancha/sim.mk
Likely all the above could've been done better with bazel because a large portion of our repo uses that but I was worried that re-implementing much of sdk/xmake.lua in bazel would be a sisyphean task given the velocity of changes. The above also ignores all the infrastructure we have for CI and hw testing.
On Fri, Oct 4, 2024 at 8:05 AM ruki @.***> wrote:
sdkdir = os.getenv("SDK") or "../../sdk"includes(sdkdir)
SDK=/xxxx/sdk xmake
— Reply to this email directly, view it on GitHub https://github.com/CHERIoT-Platform/cheriot-rtos/issues/304#issuecomment-2393917664, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABRM7IIYPI57NZMF5YMVILLZZ2VBZAVCNFSM6AAAAABPJX5ZXCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGOJTHEYTONRWGQ . You are receiving this because you are subscribed to this thread.Message ID: <CHERIoT-Platform/cheriot-rtos/issues/304/2393917664 @.***>
A couple of questions/clarifications about build2
:
a fairly new build system
The project is almost 10 years old. The first commit has the 2014-12-03 date (and there were months before that thinking with a pen and paper).
Provide a flow to generate compile_commands.json (not yet working out of the box and a must-have feature).
I am actually wrapping up the compilation database support and should have it available in the staged toolchain in a few days. Will ping this issue when it's ready with a link to the documentation.
I don't like the fact that compilation is conceptually a separate step. To me, compilation flags depend on the target (library, executable, whatever) and the fact that you have to declare separate
cxx
things is not ideal.
Hm, I am not sure I follow. Compilation is definitely not a separate steps if we are talking about the build execution. Could you expand on what you mean here?
I also don't like the fact that every project needs three files in two directories to build.
You can have a project with a single buildfile
though it has some limitations. We've been gradually lifting these limitations replacing them with sensible defaults. So we can definitely explore supporting what you have in mind with a single buildfile
.
The project is almost 10 years old. The first commit has the 2014-12-03 date (and there were months before that thinking with a pen and paper).
CMake is 24 years old, Make is from the dawn of the Epoch. Anything from this Millennium (xmake, build2, bazel, buck2) is new to me.
I am actually wrapping up the compilation database support and should have it available in the staged toolchain in a few days. Will ping this issue when it's ready with a link to the documentation.
Yay!
Hm, I am not sure I follow. Compilation is definitely not a separate steps if we are talking about the build execution. Could you expand on what you mean here?
In the rule descriptions that I read, you had cxx
rules and then compartment
rules that depended on the cxx
rules.
This is one of the things I quite like about xmake: Rules have a map-reduce structure. There's a map phase (take a load of inputs, turn it into an equal number of outputs) and a reduce phase (take those outputs and combine them into a smaller number of outputs). Both of those steps are optional, but they follow my mental model of how I build software. The .cc -> .o step is not a distinct rule, it is an implementation detail of how I build a program/library/compartment.
You can have a project with a single buildfile though it has some limitations. We've been gradually lifting these limitations replacing them with sensible defaults. So we can definitely explore supporting what you have in mind with a single buildfile.
Great! I really want people to have to read/write/maintain the smallest possible amount of boilerplate.
The .cc -> .o step is not a distinct rule, it is an implementation detail of how I build a program/library/compartment.
True typically, but not generally. The first example that comes to mind is unit testing: for a unit test you may need to link directly to the object file of a library rather than to the library itself (in order to get access to non-exported bits). So in build2
we chose to make the model general enough to accommodate such use-cases but also support automatic intermediate dependency synthesis (o: cc
) for the typical case. The result is that as a user you are not exposed to such intermediate targets until you need to. Perhaps xmake
does this differently, but the end result for the typical case should look the same, at least from the user's perspective.
Ok, the compilation database support is now available in the latest staged build2
toolchain. There is also the Compilation Database section in the manual with the documentation. And I've added an example to the BUILD2-README.md
in my fork of cheriot-rtos
.
There is one interesting question/observation: because we build the cheriot-rtos
components as part of the user project, we end up with compilation commands for all the OS and system libraries that are pulled by the project. Not sure if this is desirable. If not, we may need to add some more filtering facilities (e.g., based on source path) since I don't think the ones we have (input/output target type) will be sufficient. This is what the database contains for the hello_world
example:
[
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/loader/boot-c.o","file":".../cheriot-rtos/sdk/core/loader/boot.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DDEBUG_LOADER=false","-DNDEBUG","-DCHERIOT_LOADER_STACK_SIZE=1024","-DCHERIOT_LOADER_TRUSTED_STACK_SIZE=192","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-O1","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/loader/boot-c.o","-c","-x","c++",".../cheriot-rtos/sdk/core/loader/boot.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/loader/boot-s.o","file":".../cheriot-rtos/sdk/core/loader/boot.S","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DDEBUG_LOADER=false","-DNDEBUG","-DCHERIOT_LOADER_STACK_SIZE=1024","-DCHERIOT_LOADER_TRUSTED_STACK_SIZE=192","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/loader/boot-s.o","-c","-x","assembler-with-cpp",".../cheriot-rtos/sdk/core/loader/boot.S"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/switcher/entry.o","file":".../cheriot-rtos/sdk/core/switcher/entry.S","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/switcher/entry.o","-c","-x","assembler-with-cpp",".../cheriot-rtos/sdk/core/switcher/entry.S"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memcmp.o","file":".../cheriot-rtos/sdk/lib/freestanding/memcmp.c","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memcmp.o","-c","-x","c",".../cheriot-rtos/sdk/lib/freestanding/memcmp.c"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/locks/semaphore.o","file":".../cheriot-rtos/sdk/lib/locks/semaphore.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/locks/semaphore.o","-c","-x","c++",".../cheriot-rtos/sdk/lib/locks/semaphore.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/atomic1/atomic1.o","file":".../cheriot-rtos/sdk/lib/atomic/atomic1.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/atomic1/atomic1.o","-c","-x","c++",".../cheriot-rtos/sdk/lib/atomic/atomic1.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memcpy.o","file":".../cheriot-rtos/sdk/lib/freestanding/memcpy.c","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memcpy.o","-c","-x","c",".../cheriot-rtos/sdk/lib/freestanding/memcpy.c"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/atomic4/atomic4.o","file":".../cheriot-rtos/sdk/lib/atomic/atomic4.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/atomic4/atomic4.o","-c","-x","c++",".../cheriot-rtos/sdk/lib/atomic/atomic4.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/hello.o","file":".../cheriot-rtos/examples/01.hello_world/hello.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-cheri-compartment=hello","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/hello.o","-c","-x","c++",".../cheriot-rtos/examples/01.hello_world/hello.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/locks/locks.o","file":".../cheriot-rtos/sdk/lib/locks/locks.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/locks/locks.o","-c","-x","c++",".../cheriot-rtos/sdk/lib/locks/locks.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memset.o","file":".../cheriot-rtos/sdk/lib/freestanding/memset.c","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/freestanding/memset.o","-c","-x","c",".../cheriot-rtos/sdk/lib/freestanding/memset.c"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/allocator/main.o","file":".../cheriot-rtos/sdk/core/allocator/main.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DDEBUG_ALLOCATOR=false","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-cheri-compartment=alloc","-fvisibility=hidden","-fvisibility-inlines-hidden","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/allocator/main.o","-c","-x","c++",".../cheriot-rtos/sdk/core/allocator/main.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/crt/cz.o","file":".../cheriot-rtos/sdk/lib/crt/cz.c","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DNDEBUG","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/lib/crt/cz.o","-c","-x","c",".../cheriot-rtos/sdk/lib/crt/cz.c"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/token_library/token_unseal.o","file":".../cheriot-rtos/sdk/core/token_library/token_unseal.S","arguments":["/cheriot-tools/bin/clang","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DNDEBUG","-DDEBUG_TOKEN_LIBRARY=false","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-fvisibility=hidden","-std=c2x","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/token_library/token_unseal.o","-c","-x","assembler-with-cpp",".../cheriot-rtos/sdk/core/token_library/token_unseal.S"],"directory":".../cheriot-rtos/examples/01.hello_world"},
{"output":".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/scheduler/hello_world.main.o","file":".../cheriot-rtos/sdk/core/scheduler/main.cc","arguments":["/cheriot-tools/bin/clang++","-I.../cheriot-rtos/sdk/include/c++-config","-I.../cheriot-rtos/sdk/include/libc++","-I.../cheriot-rtos/sdk/include","-I.../cheriot-rtos/sdk/include/../include/platform/generic-riscv","-I.../cheriot-rtos/sdk/include/../include/platform/ibex","-DTEMPORAL_SAFETY","-DIBEX","-DIBEX_SAFE","-DCPU_TIMER_HZ=100000","-DTICK_RATE_HZ=10","-DREVOKABLE_MEMORY_START=0x20040000","-DCONFIG_MSHWM","-DCHERIOT_INTERRUPT_NAMES=RevokerInterrupt=1","-DSIMULATION","-DDEVICE_EXISTS_clint","-DDEVICE_EXISTS_plic","-DDEVICE_EXISTS_revoker","-DDEVICE_EXISTS_uart","-DDEVICE_EXISTS_shadow","-DCHERIOT_AVOID_CAPRELOCS","-DSCHEDULER_ACCOUNTING=false","-DDEBUG_SCHEDULER=false","-DCHERIOT_INTERRUPT_CONFIGURATION={1,2,false}","-DCONFIG_THREADS_NUM=1","-Qunused-arguments","-mcpu=cheriot","-mabi=cheriot","-mxcheri-rvc","-mrelax","-fshort-wchar","-fomit-frame-pointer","-fno-builtin","-fno-exceptions","-fno-asynchronous-unwind-tables","-fno-c++-static-destructors","-fno-rtti","-Oz","-g","-Werror","-cheri-compartment=sched","-std=c++2a","-target","riscv32-unknown-unknown","-nostdinc","-fdiagnostics-color","-finput-charset=UTF-8","-o",".../cheriot-rtos/examples/01.hello_world/cheriot-rtos/core/scheduler/hello_world.main.o","-c","-x","c++",".../cheriot-rtos/sdk/core/scheduler/main.cc"],"directory":".../cheriot-rtos/examples/01.hello_world"}
]
It is useful to have compile-commands for everything (it’s also useful to have entries for header files), because it helps cross referencing. Clangd will support jump-to-definition for various system features and having a compile commands entry for the target file means syntax highlighting works there as well (and, if that file is specialised based on board-specific things, having a per-project entry means that this shows up correctly).
We include a compile_flags.txt which is used when compile_commands.json is missing, but it’s helpful if the build system can generate a compile_commands.json entry for each header that’s used (ideally preferring a C++ target to a C one).
I’ve added some things I our build2 branch, I’ll try to merge your improvements into it.
it’s also useful to have entries for header files it’s helpful if the build system can generate a
compile_commands.json
entry for each header that’s used (ideally preferring a C++ target to a C one).
Uh, I didn't know about having entries for headers in compile_commands.json
but apparently it is a thing, though with unknown/poorly specified semantics. For once, headers are not compiled (unless we are talking about header units) so it's unclear where the corresponding command line comes from. There is a long (and still open) issue for this on CMake: https://gitlab.kitware.com/cmake/cmake/-/issues/16285
The good news is that we know the list of headers each project contains and can even accurately say whether foo.h
is a C or C++ header (because of those h{}
and hxx{}
things). But it's unclear where the compilation command would come from. Maybe we could synthesize something plausible based on what options are visible from the header's scope...
I think header ‘compile’ lines just need to be the compile lines for a file that includes the header for clangd to be able to do the right thing, but I’m not 100% sure.
We try to make our headers all work as stand-alone compilation units (though clang doesn’t like #pragma once
in the main file of a compilation unit) so our compile_flags.txt mostly works, but we can’t make that a requirement on every third-party header.
We originally built CHERIoT RTOS with CMake. This had a number of problems because CMake is not designed to allow users to build abstractions. We replaced it with xmake prior to open sourcing, and xmake is mostly fine.
Requirements
We need to be able to build four kinds of things:
Ideally, we also want to be able to add other composable chunks, which can be made dependencies of compartments or provided separately, including:
Shared libraries and compartments have a custom link step, as do firmware images.
Firmware images need to be able to walk their dependency graph and build a linker script containing all of the libraries and compartments.
Board description files (JSON, kind-of JSON5, could be YAML) need to be parsed to generate compiler flags, inputs to the firmware linker script, and so on.
The build system must be able to generate
compile_commands.json
files for IDE integration. It's 2024, this is no longer a 'nice to have' optional extra.For secure build environments, we must be able to do builds from a read-only mount of the source tree. In-tree builds are an antipattern.
We should be able to build abstractions that allow very concise definitions of projects. The current xmake build system is fairly good here. Going through the simplest hello-world example line by line:
This is slightly redundant because we need to name firmware images and so the overall project name is not very meaningful.
This is quite ugly. We are forced to hard-code the path of the SDK (more on this later) and then, because of xmake's scoping, also specify the toolchain here, even though the SDK files already specified it.
This is necessary because we don't advertise the board option from the SDK, we make each firmware select it. This is probably the right choice. Some projects may want to build firmware for different projects (the build2 proposed solution here is to have different build configs for each), others may not allow the board to be an option because they're specialised for a single SoC / board.
Again, this is concise. We are defining a compartment called
hello
, it depends on thefreestanding
anddebug
libraries (could be compartments). It has a single source file,hello.cc
. This will be compiled with the default options for a compartment and linked as a compartment.We're starting to define a firmware image called
hello_world
, it depends on thehello
compartment.Now we see some of the less-nice bits of xmake. You can't extend the xmake declarative syntax, you can only add properties in the imperative world, so we now need to write a Lua function that runs when this target is loaded.
In declarative syntax, this would be simple
set_board("$(board)")
, but that's a small issue.This defines a single thread, its entry point, and so on. It would be nice to have this be a type that could be automatically type checked (the build2 prototype does this), but this is largely fine.
The really ugly thing here is the
{ expand = false }
. This is not properly documented in the xmake docs, but is essential to avoid xmake flattening the object that you've put here.Problems with xmake
Although xmake works, we have encountered a lot of problems over time.
Poor defaults
A build system should loudly complain if things are wrong.
If you add a nonexistent file (for example, add it with a
.c
extension instead of.cc
, or fail to commit it to git), xmake does not complain. It simply builds and, if the link step works, the build succeeded. You probably didn't need the file anyway. We had a test that the FreeRTOS compat headers all compiled in C mode that was spuriously passing for months as a result of this (I added the xmake bits, forgot to add the .c file, and CI passed).By default, xmake hid compiler warnings (this was fixed in v2.8.7). This is the worst possible default. The argument from the maintainer was that 'most users' didn't need to see them. I believe that most users of a build system are developers and they absolutely need to see warnings. The second most common users are people building packages, and they should see warnings because they may help debug problems. The third category are people who are building things from source but are not developers, and these people are a rounding error and probably don't care either way.
If you add a compiler flag, xmake will drop it if it isn't supported. It doesn't try passing it to the compiler to see if it's supported, it has some internal model of the compiler (which may or may not be relevant for a cross-compile toolchain) and ignores it. You must add
{force = yes }
(which was undocumented the first time I needed it, I'm not sure if it's documented now) to make xmake do the thing that you explicitly told it to do.Similarly, if you pass a positional argument with any of the flag-adding functions, even with
{force = true}
, and xmake doesn’t know about the argument, then it will deduplicate it. Things like-Xclang
or-mllvm
, which need to be passed multiple times are silently removed. There is no documented way of avoiding that, but there is an undocumented way, which is to pass the pair of flags as a Lua array and addexpand = false
in the last argument.Everything in xmake is a string. The type of a build rule is a string. If you make a typo, it will silently make the target use the default rule.
Insecure searching
Unless you explicitly specify a target path, xmake looks up the directory tree to find the
xmake.lua
furthest from you. This may be across a mount point and controlled by a different user. This is an incredibly insecure default.Poor scoping
Targets in xmake must have globally unique names. Some things are local to the file, others are not. For example, you must set the toolchain in the top-level file, you can't provide it in the SDK file.
Extensions are second class
Internally, xmake defines a lot of different target types. These have access to a load of infrastructure that is exposed. Anything that is implemented externally lacks access to this infrastructure and so cannot work as well as things built into the build system.
Opaque dependency tracking and poorly specified execution order
We have several custom rules. It's not clear how to tell xmake that a given file is a dependency of them. Sometimes, the only way to get a safe rebuild is
rm -rf .xmake build
.xmake also provides its own caching, which is not invalidated when the toolchain is updated. This breaks reproducible builds and introduces bugs when a new toolchain feature is required.
Options are not processed until after the declarative syntax is processed (and the declarative syntax isn't really declarative). This means that you can't use an option to provide the SDK path. We'd like to be able to have build files that have a default path for the SDK, but let you override it. With xmake, we need to modify
xmake.lua
.Similarly, it's unclear what order the
on_load
functions for different targets will run in. We tried using the support that was added for dynamically cloning targets for the allocator, but this was impossible because the new target'son_load
things didn't run.There probably is some internal model for how builds work, but the author has not written it down anywhere.
Other options
There are a few potentially promising alternatives that we should explore before we do a 1.0 release and need to support whatever we ship for a long time. For some reason, they seem to be things that match the pattern
B.*2
.Build2
Build2 is a fairly new build system that is designed to support modern C++ features. @boris-kolpackov has built us a proof of concept that can build the hello-world example. We need to:
compile_commands.json
(not yet working out of the box and a must-have feature).It looks quite promising. I don't like the fact that compilation is conceptually a separate step. To me, compilation flags depend on the target (library, executable, whatever) and the fact that you have to declare separate
cxx
things is not ideal.I also don't like the focus on short names in build2. These are hard to search for and they place a higher cognitive load on the programmer. Most developers will spend 1% of their time touching the build system, so a build system needs to be easy for people to context switch back to. I find a lot of the build2 logic hard to read.
I also don't like the fact that every project needs three files in two directories to build.
Buck2
Buck2 is a rewrite of Facebook's Buck build tool. It uses Starlark, a Python-like language, for scripting and exposes all of the rules, which means that our build rules would be equivalent to first-class rules from other places.
Buck2 is written in Rust and provides a single statically linked binary that is easy to deploy.
We currently have no proof-of-concept in Buck2, but it is probably worth exploring.
Others?
CMake was used originally and its limitations were too great.
Bazel has some nice properties, but it has too big a dependncy chain. Bazel requires both a working Java VM and a Python installation. This is fragile and far too big a supply-chain attack surface for something in every build.
tup / make are low-level tools that rely on third-party things if you need abstractions. We don't want to maintain a tup generator.
Any others that we should evaluate?