1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
#!/bin/zsh -e
MICROARCH="skylake"
TARGET=x86_64-maple-linux-musl
# Set the environment up
ARCH=$(echo $TARGET | cut -d"-" -f1)
BOOTSTRAP=$(pwd)/.bootstrap
PROCS=$(nproc)
SOURCES=$(pwd)/.treetap/sources
export AR=llvm-ar
export CC=clang
export CFLAGS="-fuse-ld=lld -O3 -march=$MICROARCH -pipe --sysroot=$BOOTSTRAP/root -Wno-unused-command-line-argument"
export CXX=clang++
export CXXFLAGS=$CFLAGS
export RANLIB=llvm-ranlib
export LD=ld.lld
export TREETAP_SYSROOT=$BOOTSTRAP/root
export TREETAP_TARGET=$TARGET
# Fetch sources required for a bootstrap
./treetap fetch sources/busybox.spec
./treetap fetch sources/linux.spec
./treetap fetch sources/llvm.spec
./treetap fetch sources/musl.spec
# Simplified filesystem heirarchy with symlinks for compatibility
mkdir -p $BOOTSTRAP/root/{bin,boot/EFI/BOOT,dev,etc,home,lib,proc,run,sys,tmp,usr/{include,share},var/{cache,lib,log,spool,tmp}}
ln -sf bin $BOOTSTRAP/root/sbin
ln -sf ../bin $BOOTSTRAP/root/usr/bin
ln -sf ../bin $BOOTSTRAP/root/usr/sbin
ln -sf ../lib $BOOTSTRAP/root/usr/lib
ln -sf ../lib $BOOTSTRAP/root/usr/libexec
ln -sf ../run/lock $BOOTSTRAP/root/var/lock
ln -sf ../run $BOOTSTRAP/root/var/run
# Prepare for the build
mkdir -p $BOOTSTRAP/build
cd $BOOTSTRAP/build
# Define the target for Maple Linux
cat << EOF > $BOOTSTRAP/$TARGET.cmake
set(CMAKE_ASM_COMPILER_TARGET $TARGET)
set(CMAKE_C_COMPILER $CC)
set(CMAKE_C_COMPILER_TARGET $TARGET)
set(CMAKE_C_FLAGS_INIT "$CFLAGS")
set(CMAKE_CXX_COMPILER $CXX)
set(CMAKE_CXX_COMPILER_TARGET $TARGET)
set(CMAKE_CXX_FLAGS_INIT "$CXXFLAGS")
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_LINKER_TYPE LLD)
set(CMAKE_SYSROOT "$BOOTSTRAP/root")
set(CMAKE_SYSTEM_NAME Linux)
EOF
# Install headers for Linux
tar xf $SOURCES/linux/*/linux-*.tar*
cd linux-*/
# NOTE: LLVM=1 is required here because GCC and other GNU tools are required in
# some places still. This allows us to use LLVM and bypass the parts that
# haven't become portable yet. ~ahill
LLVM=1 make -j $PROCS mrproper
# NOTE: We don't use the built-in headers_install target because it requires
# rsync for some reason. ~ahill
LLVM=1 make -j $PROCS headers ARCH=$ARCH
find usr/include -type f ! -name "*.h" -delete
cp -r usr/include $BOOTSTRAP/root/usr
cd ..
# Install headers for musl
tar xf $SOURCES/musl/*/musl-*.tar*
cd musl-*/
# NOTE: We are intentionally not passing --target here because musl follows the
# GNU approach when it comes to cross-compiling. This means the build
# script prefaces the name of every build tool with the target triple
# we're compiling for. This is unnecessary for LLVM, because we can simply
# pass -target <triple> to the compiler and have it generate machine code
# for that target. ~ahill
./configure \
--bindir=/bin \
--includedir=/usr/include \
--libdir=/lib \
--prefix=/
# NOTE: The build is skipped here because we only care about the header files at
# this point. ~ahill
make -O -j $PROCS install-headers DESTDIR=$BOOTSTRAP/root
cd ..
# Build and install compiler-rt builtins
tar xf $SOURCES/llvm/*/llvm-project-*.tar*
cd llvm-project-*/
cmake -S compiler-rt/lib/builtins -B build-builtins \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$BOOTSTRAP/root \
-DCMAKE_TOOLCHAIN_FILE=$BOOTSTRAP/$TARGET.cmake \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
cmake --build build-builtins --parallel $PROCS
cmake --install build-builtins --parallel $PROCS
cd ..
# Build musl for real this time
cd musl-*/
# FIXME: Patch CVE-2025-26519 ~ahill
make clean
# NOTE: LIBCC is required here because it will attempt to link with the build
# system's runtime if this is not specified. ~ahill
LIBCC="$BOOTSTRAP/root/lib/linux/libclang_rt.builtins-x86_64.a" \
./configure \
--bindir=/bin \
--includedir=/usr/include \
--libdir=/lib \
--prefix=/
make -O -j $PROCS
make -O -j $PROCS install DESTDIR=$BOOTSTRAP/root
cd ..
# Include compiler-rt and musl in our environment
export CFLAGS="$CFLAGS -Qunused-arguments -rtlib=compiler-rt -Wl,--dynamic-linker=/lib/ld-musl-$ARCH.so.1"
export CXXFLAGS="$CXXFLAGS -Qunused-arguments -rtlib=compiler-rt -Wl,--dynamic-linker=/lib/ld-musl-$ARCH.so.1"
# Build the LLVM runtimes
# NOTE: When CMake tests the C++ compiler, it attempts to link libstdc++/libc++
# before it even exists, causing an error. We can bypass this by simply
# setting CMAKE_CXX_COMPILER_WORKS, therefore tricking CMake into
# performing a successful build. Yet another instance where the genie in
# the bottle does exactly what we asked, and not what we wanted. ~ahill
# NOTE: Not sure why this didn't show up before, but CMAKE_C_COMPILER_WORKS is
# is manually set because the C compiler attempts to link with libgcc_s or
# libunwind, even though it doesn't exist yet. ~ahill
cd llvm-project-*/
cmake -S runtimes -B build-runtimes \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_WORKS=ON \
-DCMAKE_CXX_COMPILER_WORKS=ON \
-DCMAKE_INSTALL_INCLUDEDIR=$BOOTSTRAP/root/usr/include \
-DCMAKE_INSTALL_PREFIX=$BOOTSTRAP/root \
-DCMAKE_TOOLCHAIN_FILE=$BOOTSTRAP/$TARGET.cmake \
-DCOMPILER_RT_USE_BUILTINS_LIBRARY=ON \
-DLIBCXX_CXX_ABI=libcxxabi \
-DLIBCXX_HAS_MUSL_LIBC=ON \
-DLIBCXX_USE_COMPILER_RT=ON \
-DLIBCXXABI_USE_COMPILER_RT=ON \
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
-DLIBUNWIND_USE_COMPILER_RT=ON \
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind"
cmake --build build-runtimes --parallel $PROCS
cmake --install build-runtimes --parallel $PROCS
cd ..
# Now we can introduce libunwind and libc++ into the environment
# NOTE: clang++ attempts to build with headers from the build system rather than
# exclusively relying on the sysroot. Because of this, we must manually
# define the proper include path. ~ahill
export CFLAGS="$CFLAGS -unwindlib=libunwind"
export CXXFLAGS="$CXXFLAGS -isystem $BOOTSTRAP/root/usr/include/c++/v1 -nostdinc++ -stdlib=libc++ -unwindlib=libunwind"
# Build clang/LLVM
# NOTE: LLVM_ENABLE_ZSTD is disabled because we don't have zstd in the sysroot,
# and because I don't believe that a library created by Facebook should
# be required for an operating system to function. ~ahill
# NOTE: If we don't do this, LLVM attempts to build its own copy of tblgen,
# which will cause the build to fail since we're cross-compiling and can't
# run programs built for another platform. LLVM_NATIVE_TOOL_DIR,
# LLVM_TABLEGEN, and CLANG_TABLEGEN are set based on this variable. ~ahill
NATIVE_TOOL_DIR=$(dirname $(which llvm-tblgen))
cd llvm-project-*/
cmake -S llvm -B build-llvm \
-DCLANG_DEFAULT_CXX_STDLIB=libc++ \
-DCLANG_DEFAULT_RTLIB=compiler-rt \
-DCLANG_DEFAULT_UNWINDLIB=libunwind \
-DCLANG_TABLEGEN=$NATIVE_TOOL_DIR/clang-tblgen \
-DCLANG_VENDOR=Maple \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$BOOTSTRAP/root \
-DCMAKE_TOOLCHAIN_FILE=$BOOTSTRAP/$TARGET.cmake \
-DLLVM_ENABLE_LIBCXX=ON \
-DLLVM_ENABLE_PROJECTS="clang;lld;llvm" \
-DLLVM_ENABLE_ZSTD=OFF \
-DLLVM_HOST_TRIPLE=$TARGET \
-DLLVM_NATIVE_TOOL_DIR=$NATIVE_TOOL_DIR \
-DLLVM_TABLEGEN=$NATIVE_TOOL_DIR/llvm-tblgen
cmake --build build-llvm --parallel $PROCS
cmake --install build-llvm --parallel $PROCS
cd ..
# Build Busybox
tar xf $SOURCES/busybox/*/busybox-*.tar*
cd busybox-*/
# NOTE: Like we did with musl before, we don't set CROSS_COMPILE because LLVM is
# smart and doesn't need a compiler to cross-compile code. With that said,
# Busybox uses Kbuild, which hard-codes variables like CC to GNU-based
# tools, which is not what we want. The following sed hack should do the
# trick, but I wonder if there's a better solution. ~ahill
sed -i "s/?*= \$(CROSS_COMPILE)/?= /" Makefile
make -O -j $PROCS defconfig
# NOTE: tc causes a LOT of issues due to undefined things. We don't really need
# "traffic control" at this time, and when we eventually do, we will
# likely use something else. ~ahill
sed -i "s/CONFIG_TC=.*/CONFIG_TC=n/" .config
make -O -j $PROCS
# NOTE: Busybox doesn't appear to have a proper DESTDIR, so we just set
# CONFIG_PREFIX during the install step to work around this. ~ahill
make -O -j $PROCS install CONFIG_PREFIX=$BOOTSTRAP/root
cd ..
# Install Treetap
cp $BOOTSTRAP/../treetap $BOOTSTRAP/root/bin/
|