creating-new-build-systems.html 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. <!DOCTYPE html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /><meta name="keywords" content="GNU, Emacs, Libre Software, Hurd, Guile, Guix" /><meta name="description" content="GNUcode.me is a website focusing on libre software projects, especially the GNU project." /><link type="application/atom+xml" rel="alternate" title="GNUcode.me -- Feed" href="/feed.xml" /><a rel="me" href="https://fosstodon.org/@thegnuguy"></a><link type="text/css" href="css/footer.min.css" rel="stylesheet"></link><link type="text/css" href="css/header.min.css" rel="stylesheet"></link><link type="text/css" href="css/main.min.css" rel="stylesheet"></link><title>Creating New Build Systems — GNUcode.me</title></head><body><header><nav><ul><li><a href="index.html">GNUcode.me</a></li><li><a href="services.html">Services</a></li><li><a href="about.html">About</a></li><li><a href="business-ideas.html">Business-ideas</a></li></ul></nav></header><h1>Creating New Build Systems</h1><main><section class="basic-section-padding"><article><h3>by Mitchell Schmeisser &lt;mitchellschmeisser@librem.one&gt; — February 24, 2023</h3><div><p>In Guix each package must specify a so-called <code>build-system</code>, which
  2. knows how to bring a package from its inputs to deployment.
  3. Build systems are responsible for setting up the environment and performing
  4. build actions within that environment. The most ubiquitous of these is the
  5. <a href="https://www.gnu.org/software/automake/manual/html_node/GNU-Build-System.html"><code>gnu-build-system</code></a>.
  6. Guix builds packages using this build system via the usual
  7. <code>./configure &amp;&amp; make &amp;&amp; make install</code> process.</p><p>Any package can alter its build system by removing some steps or
  8. adding extra ones. This is extremely common and almost every package
  9. makes some adjustment to the build process. A <code>build-system</code> in Guix
  10. hides away some of the common configuration choices. For example, there is
  11. no need to specify <code>make</code> or <code>gcc</code> as native inputs when using the <code>gnu-build-system</code>,
  12. because they are added implicitly when a package is lowered into a <em>bag</em>.</p><h2>Anatomy of a Guix Build System</h2><p>The job of a build system is to compile our <em>packages</em> into <em>bags</em>.
  13. Bags are a lower level representation of a package without all the bells and whistles
  14. (Makes sense since we are implementing them here),
  15. the bags are further refined to derivations which are used by the
  16. build daemon to create an isolated environment suitable for our
  17. <em>build phases</em>.</p><p>Below is how guix defines a build system.
  18. It's surprisingly simple with just three items, two of which are for human consumption.</p><pre><code class="language-scheme">(define-record-type* &lt;build-system&gt; build-system make-build-system
  19. build-system?
  20. (name build-system-name) ; symbol
  21. (description build-system-description) ; short description
  22. (lower build-system-lower)) ; args ... -&gt; bags</code></pre><p>The last field <code>lower</code> is a function which takes the list of arguments
  23. given in the <code>(package ... (arguments ...))</code> field.
  24. The keyword arguments that we are allowed to supply when writing the
  25. package are defined by the build-system.</p><h1>Code Strata</h1><p>Guix builds are implemented in two parts.</p><ol><li><p>Code that compiles <code>packages-&gt;derivations</code>.</p><p>Derivations are the language the Guix Daemon speaks.
  26. They describe everything about how to <em>derive</em> our package
  27. from the inputs to the environment and all the code on how to drive
  28. the build tools.
  29. This code is run in a poorly defined &quot;user&quot; environment.
  30. Guix produces derivations that actually can be influenced by
  31. undeclared aspects of the environment, like manually installed Guile
  32. packages or code added with the `-L` flag.</p></li><li><p>The guix daemon runs the builder code in as isolated and reproducible build environment to produce the package from its inputs.</p><p>This code is executed in an explicitly defined build environment
  33. with nothing being introduced from the host.</p></li></ol><p>Code that runs in the host environment <em>stages</em> code, which will run in isolation.
  34. This is where G-Expressions really shine.
  35. They provide the syntax to describe this relationship.</p><h1>Build Phases</h1><p>All programs are built more or less the same way.</p><ol><li><p>Unpack the source code.</p><p>Whether it's tarball or a version controlled repository, the guix daemon must
  36. copy the software's source tree into our build environment.</p></li><li><p>Patch the shebangs.</p><p>Many projects contain scripts written to aid the build process.
  37. In Linux, executable scripts can contain a so-called <em>shebang</em>,
  38. which contains an absolute path to the program, which is meant to
  39. interpret it: e.g. <code>#!/bin/sh</code>. Most distributions provide a
  40. POSIX compliant shell interpreter at this location. Guix System does not do this.
  41. Instead, Guix System's <code>sh</code> is yet another component in the store, so all of these files must
  42. be patched to point to the new location, which is only known at
  43. build time. We cannot rely on our <code>PATH</code> to store this information,
  44. because the Linux kernel does not respect such things.</p></li><li><p>Configure the build.</p><p>Whether it is autotools or CMake or ninja, etc., if you are relying
  45. on tools from the host system, then you have a step, which enables the
  46. host system to tell you where to find those tools.</p></li><li><p>Patch the generated shebangs.</p><p>Sometimes the configure phases creates scripts, which run during the build phase,
  47. these often contain references to <code>/bin/sh</code>, and so guix must patch these scripts as well.</p></li><li><p>Build!</p><p>That's right folks we are off to the races, and the program is building.
  48. Usually this takes the form of a call to <code>make</code>, with a handful of
  49. flags and last minute definitions, but there are other more <em>exotic</em> methods.</p></li><li><p>Test.</p><p>Now that guix built our software, we should test it before we sent software
  50. updates to users. This helps the guix project catch and eleminate software
  51. bugs before they impact guix users. Not all packages have tests, and guix
  52. developers occasionally disables some packages' testsuites, but the
  53. guix policy is to run the software's testsuite, if it is exists.</p></li><li><p>Install.</p><p>Now that we've verified everything works as expected, it is time to run
  54. <code>make install</code> or the equivalent. This phase copies our build artifacts into
  55. their final resting place in the store.</p></li><li><p>Validate the Installation.</p><p>Here guix checks that our outputs do not contain any component paths, which
  56. are not specified by the package inputs. That would lead to incomplete
  57. deployment, harm <a href="https://reproducible-builds.org/">reproducible builds</a>,
  58. and would be &quot;bad&quot;.</p></li></ol><p>There are more steps, but they are not universally applicable.
  59. Of course no generic model, such as this, captures all the chaos of the
  60. world, and every package has exceptions to it.</p><p>Guix implements <em>build phases</em> as a list of lambdas.
  61. As such our package can modify this list and add/delete/replace
  62. lambdas as they require. It is so common Guix provides a syntax
  63. for manipulating build phases: <code>modify-phases</code>.</p><p>A build system contains a default set of phases called <code>%standard-phases</code>.
  64. Every build system starts with the <code>gnu-build-system</code>, <code>%standard-phases</code>,
  65. and modifies it to their needs.
  66. It is then provided to the packages using that build system.</p><p>The <code>cmake-build-system</code> is nearly identical to the <code>gnu-build-system</code>
  67. except two phases.</p><pre><code class="language-scheme">;;
  68. ;; Excerpt from `guix/build/cmake-build-system.scm`
  69. ;;
  70. (define %standard-phases
  71. ;; Everything is the same as the GNU Build System, except for the `configure'
  72. ;; and 'check' phases.
  73. (modify-phases gnu:%standard-phases
  74. (delete 'bootstrap)
  75. (replace 'check check)
  76. (replace 'configure configure)))</code></pre><h2>The Zephyr Build System</h2><p>Zephyr uses <code>cmake</code> to perform <em>physical component composition</em>.
  77. It searches the filesystem and generates scripts, which the toolchain will
  78. use to successfully combine those components into a firmware image.</p><p>The fact that Zephyr provides this mechanism is one reason I chose to
  79. target zephyr in the first place.</p><p>This separation of projects in an embedded context is a really great thing.
  80. It brings many of the advantages to the Linux world such as
  81. code re-use, smaller binaries, more efficient cache/RAM usage, etc.
  82. It also allows us to work as independent groups and compose
  83. contributions from many teams.</p><p>It also brings all of the complexity. Suddenly all of the problems
  84. that plague traditional deployment now apply to our embedded
  85. system. The fact that the libraries are statically linked at compile
  86. time instead of dynamically at runtime is simply an implementation detail.</p><p>We now have dependencies which we must track and compose in the correct environments.
  87. The Zephyr Project offers a tool to help manage this new complexity: <a href="https://docs.zephyrproject.org/latest/develop/west/index.html">The West Meta-tool</a>!
  88. To call it a &quot;meta-tool&quot; is an abuse of language. It is not even meta
  89. enough to bootstrap itself and relies on <a href="https://docs.zephyrproject.org/latest/develop/getting_started/index.html">other package managers</a> to get
  90. started.
  91. In fact, it is not even suitable for managing multiple embedded projects as a composition!
  92. The project recommends <a href="https://docs.zephyrproject.org/latest/build/sysbuild/index.html">Yet Another Tool</a> for this very common use case.</p><p>In fact, West does not really bring anything new to the table, and we can
  93. replace it in the same way we can replace every other language specific package
  94. manager, like node (js), Cabol (haskell), Dub (D), etc. <strong>PUTTING SOFTWARE ON
  95. THE SYSTEM IS THE JOB OF THE PACKAGE MANAGER!</strong>.</p><p>West is the way it is for a reason. It is very practical to design the package
  96. manager in this way, because it enables Windows users to access the build
  97. environment. A more in-depth discussion on the material conditions, which lead
  98. to this or that design decision of the West tool is beyond the scope of this
  99. post. Let's instead explain how to provide a meta-tool and bootstrap complex
  100. embedded systems, from the ground up, in a flexible, composable, and
  101. <em>reproducible</em> way.</p><h1>Why not use <code>cmake-build-system</code>?</h1><p>A fair question! Zephyr's CMake scripts require additional information
  102. about the build to be provided at configure time. Most tedius for
  103. zephyr-packages is the <code>ZEPHYR_MODULES</code> variable, which must be formatted and
  104. passed to CMake on the command line.</p><h1>Host Side</h1><p>Our job at this level is to take packages described using the <code>package</code> syntax and
  105. lower it into a derivation that the guix-daemon can understand.</p><p>Here is the derivation for building hello world for the <code>frdm_k64f</code> (hashes removed for brevity).
  106. The <code>package</code> syntax provides a human friendly veneer over this garbage.</p><pre><code class="language-sh">Derive
  107. ([(&quot;debug&quot;,&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr--debug&quot;,&quot;&quot;,&quot;&quot;)
  108. ,(&quot;out&quot;,&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr-&quot;,&quot;&quot;,&quot;&quot;)]
  109. ,[(&quot;/gnu/store/...-newlib-nano-3.3.drv&quot;,[&quot;out&quot;])
  110. ,(&quot;/gnu/store/...-hal-nxp-3.1.0-0.708c958.drv&quot;,[&quot;out&quot;])
  111. ,(&quot;/gnu/store/...-make-4.3.drv&quot;,[&quot;out&quot;])
  112. ,(&quot;/gnu/store/...-findutils-4.8.0.drv&quot;,[&quot;out&quot;])
  113. ,(&quot;/gnu/store/...-grep-3.6.drv&quot;,[&quot;out&quot;])
  114. ,(&quot;/gnu/store/...-sed-4.8.drv&quot;,[&quot;out&quot;])
  115. ,(&quot;/gnu/store/...-ld-wrapper-0.drv&quot;,[&quot;out&quot;])
  116. ,(&quot;/gnu/store/...-bash-minimal-5.1.8.drv&quot;,[&quot;out&quot;])
  117. ,(&quot;/gnu/store/...-hal-cmsis-5.8.0-0.093de61.drv&quot;,[&quot;out&quot;])
  118. ,(&quot;/gnu/store/...-gawk-5.1.0.drv&quot;,[&quot;out&quot;])
  119. ,(&quot;/gnu/store/...-gzip-1.10.drv&quot;,[&quot;out&quot;])
  120. ,(&quot;/gnu/store/...-python-six-1.16.0.drv&quot;,[&quot;out&quot;])
  121. ,(&quot;/gnu/store/...-zephyr-3.1.0-0.zephyr--checkout.drv&quot;,[&quot;out&quot;])
  122. ,(&quot;/gnu/store/...-linux-libre-headers-5.10.35.drv&quot;,[&quot;out&quot;])
  123. ,(&quot;/gnu/store/...-python-3.9.9.drv&quot;,[&quot;out&quot;])
  124. ,(&quot;/gnu/store/...-diffutils-3.8.drv&quot;,[&quot;out&quot;])
  125. ,(&quot;/gnu/store/...-arm-zephyr-eabi-nano-toolchain-12.1.0.drv&quot;,[&quot;out&quot;])
  126. ,(&quot;/gnu/store/...-python-pyelftools-0.28.drv&quot;,[&quot;out&quot;])
  127. ,(&quot;/gnu/store/...-guile-3.0.7.drv&quot;,[&quot;out&quot;])
  128. ,(&quot;/gnu/store/...-python-dateutil-2.8.2.drv&quot;,[&quot;out&quot;])
  129. ,(&quot;/gnu/store/...-patch-2.7.6.drv&quot;,[&quot;out&quot;])
  130. ,(&quot;/gnu/store/...-gcc-10.3.0.drv&quot;,[&quot;out&quot;])
  131. ,(&quot;/gnu/store/...-bzip2-1.0.8.drv&quot;,[&quot;out&quot;])
  132. ,(&quot;/gnu/store/...-dtc-1.6.1.drv&quot;,[&quot;out&quot;])
  133. ,(&quot;/gnu/store/...-gcc-cross-sans-libc-arm-zephyr-eabi-12.1.0.drv&quot;,[&quot;out&quot;])
  134. ,(&quot;/gnu/store/...-cmake-minimal-3.21.4.drv&quot;,[&quot;out&quot;])
  135. ,(&quot;/gnu/store/...-python-pyyaml-6.0.drv&quot;,[&quot;out&quot;])
  136. ,(&quot;/gnu/store/...-python-packaging-21.3.drv&quot;,[&quot;out&quot;])
  137. ,(&quot;/gnu/store/...-arm-zephyr-eabi-binutils-2.38.drv&quot;,[&quot;out&quot;])
  138. ,(&quot;/gnu/store/...-glibc-2.33.drv&quot;,[&quot;out&quot;,&quot;static&quot;])
  139. ,(&quot;/gnu/store/...-qemu-7.2.0.drv&quot;,[&quot;out&quot;])
  140. ,(&quot;/gnu/store/...-ninja-1.10.2.drv&quot;,[&quot;out&quot;])
  141. ,(&quot;/gnu/store/...-tar-1.34.drv&quot;,[&quot;out&quot;])
  142. ,(&quot;/gnu/store/...-xz-5.2.5.drv&quot;,[&quot;out&quot;])
  143. ,(&quot;/gnu/store/...-binutils-2.37.drv&quot;,[&quot;out&quot;])
  144. ,(&quot;/gnu/store/...-python-pykwalify-1.7.0.drv&quot;,[&quot;out&quot;])
  145. ,(&quot;/gnu/store/...-zephyr-3.1.0-0.zephyr-.drv&quot;,[&quot;out&quot;])
  146. ,(&quot;/gnu/store/...-glibc-utf8-locales-2.33.drv&quot;,[&quot;out&quot;])
  147. ,(&quot;/gnu/store/...-gdb-arm-zephyr-eabi-12.1.drv&quot;,[&quot;out&quot;])
  148. ,(&quot;/gnu/store/...-module-import-compiled.drv&quot;,[&quot;out&quot;])
  149. ,(&quot;/gnu/store/...-file-5.39.drv&quot;,[&quot;out&quot;])
  150. ,(&quot;/gnu/store/...-python-pyparsing-3.0.6.drv&quot;,[&quot;out&quot;])
  151. ,(&quot;/gnu/store/...-python-docopt-0.6.2.drv&quot;,[&quot;out&quot;])
  152. ,(&quot;/gnu/store/...-arm-zephyr-eabi-sdk-0.15.0.drv&quot;,[&quot;out&quot;])
  153. ,(&quot;/gnu/store/...-coreutils-8.32.drv&quot;,[&quot;out&quot;])]
  154. ,[&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr--builder&quot;,&quot;/gnu/store/...-module-import&quot;]
  155. ,&quot;x86_64-linux&quot;,&quot;/gnu/store/...-guile-3.0.7/bin/guile&quot;,[&quot;--no-auto-compile&quot;
  156. ,&quot;-L&quot;,&quot;/gnu/store/...-module-import&quot;
  157. ,&quot;-C&quot;,&quot;/gnu/store/...-module-import-compiled&quot;
  158. ,&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr--builder&quot;]
  159. ,[(&quot;debug&quot;,&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr--debug&quot;)
  160. ,(&quot;out&quot;,&quot;/gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-0.zephyr-&quot;)])</code></pre><h3>Lowering packages to bags</h3><p>We must provide the build system with a function which the following lambda form.</p><pre><code class="language-scheme">(lambda* (name #:key #:allow-other-keys) ...)</code></pre><p>This means it takes one required argument <code>name</code> and any amount of keys.
  161. Individual procedures can specify keys they are interested in such as
  162. <code>inputs</code> or <code>outputs</code>.</p><p>Which keys are ultimately supported is defined by our <code>lower</code> function
  163. and our <em>build phases</em>.</p><pre><code class="language-scheme">;; Use module-ref instead of referencing the variables directly
  164. ;; to avoid circular dependencies.
  165. (define %zephyr-build-system-modules
  166. `((mfs build zephyr-build-system)
  167. ,@%cmake-build-system-modules))
  168. (define default-zephyr-base
  169. (module-ref (resolve-interface '(mfs packages zephyr))
  170. 'zephyr))
  171. (define default-zephyr-sdk
  172. (module-ref (resolve-interface '(mfs packages zephyr))
  173. 'arm-zephyr-eabi-sdk))
  174. (define default-ninja
  175. (module-ref (resolve-interface '(gnu packages ninja))
  176. 'ninja))
  177. (define default-cmake
  178. (module-ref (resolve-interface '(gnu packages cmake))
  179. 'cmake-minimal))
  180. (define* (lower name
  181. #:key source inputs native-inputs outputs system target
  182. (zephyr default-zephyr-base)
  183. (sdk default-zephyr-sdk)
  184. (ninja default-ninja)
  185. (cmake default-cmake)
  186. #:allow-other-keys
  187. #:rest arguments)
  188. &quot;Return a bag for NAME.&quot;
  189. (define private-keywords `(#:zephyr #:inputs #:native-inputs #:target))
  190. (bag
  191. (name name)
  192. (system system)
  193. (target target)
  194. (build-inputs `(,@(if source `((&quot;source&quot; ,source)) '())
  195. ,@`((&quot;cmake&quot; ,cmake))
  196. ,@`((&quot;zephyr-sdk&quot; ,sdk))
  197. ,@`((&quot;zephyr&quot; ,zephyr))
  198. ,@`((&quot;ninja&quot; ,ninja))
  199. ,@native-inputs
  200. ,@(standard-packages)))
  201. ;; Inputs need to be available at build time
  202. ;; since everything is statically linked.
  203. (host-inputs inputs)
  204. (outputs outputs)
  205. (build zephyr-build)
  206. (arguments (strip-keyword-arguments private-keywords arguments))))</code></pre><p>Here our <code>lower</code> function provides default values for the packages
  207. every zephyr package needs, the SDK, CMake, and <code>ZEPHYR_BASE</code> and adds
  208. them to the build-inputs.</p><p>Notice we also strip out some keywords, which do not get passed to the
  209. build function, because they get included as part of the broader
  210. abstractions the build system provides.</p><p>At this step it would be great to have a parser which could work out
  211. the required sdk from a <code>.config,</code> but this requires compiling the kconfig,
  212. which requires at least the sdk cmake files.
  213. There might be a way to make it happen, but until then if a board needs a different
  214. sdk, then they can specify it in an argument keyword.</p><h3>Lowering Bags to Derivations</h3><p>Here is the definition for the actual build procedure. There is a lot
  215. of abstract trickery going on here, so do not worry if you don't understand it,
  216. I barely understand it!
  217. It's mostly copy and pasted from the CMake build system.</p><pre><code class="language-scheme">(define* (zephyr-build name inputs
  218. #:key guile source
  219. board
  220. (outputs '(&quot;out&quot;)) (configure-flags ''())
  221. (search-paths '())
  222. (make-flags ''())
  223. (out-of-source? #t)
  224. (tests? #f)
  225. (test-target &quot;test&quot;)
  226. (parallel-build? #t) (parallel-tests? #t)
  227. (validate-runpath? #f)
  228. (patch-shebangs? #t)
  229. (phases '%standard-phases)
  230. (system (%current-system))
  231. (substitutable? #t)
  232. (imported-modules %zephyr-build-system-modules)
  233. ;; The modules referenced here contain code
  234. ;; which will be staged in the build environment with us.
  235. ;; Our build gexp down below will only be able to access this code
  236. ;; and we must be careful not to reference anything else.
  237. (modules '((zephyr build zephyr-build-system)
  238. (guix build utils))))
  239. &quot;Build SOURCE using CMAKE, and with INPUTS. This assumes that SOURCE
  240. provides a 'CMakeLists.txt' file as its build system.&quot;
  241. ;; This is the build gexp. It handles staging values from our host
  242. ;; system into code that our build system can run.
  243. (define build
  244. (with-imported-modules imported-modules
  245. #~(begin
  246. (use-modules #$@(sexp-&gt;gexp modules))
  247. #$(with-build-variables inputs outputs
  248. #~(zephyr-build #:source #+source
  249. #:system #$system
  250. #:outputs %outputs
  251. #:inputs %build-inputs
  252. #:board #$board
  253. #:search-paths '#$(sexp-&gt;gexp
  254. (map search-path-specification-&gt;sexp
  255. search-paths))
  256. #:phases #$(if (pair? phases)
  257. (sexp-&gt;gexp phases)
  258. phases)
  259. #:configure-flags #$(if (pair? configure-flags)
  260. (sexp-&gt;gexp configure-flags)
  261. configure-flags)
  262. #:make-flags #$make-flags
  263. #:out-of-source? #$out-of-source?
  264. #:tests? #$tests?
  265. #:test-target #$test-target
  266. #:parallel-build? #$parallel-build?
  267. #:parallel-tests? #$parallel-tests?
  268. #:validate-runpath? #$validate-runpath?
  269. #:patch-shebangs? #$patch-shebangs?
  270. #:strip-binaries? #f)))))
  271. (mlet %store-monad ((guile (package-&gt;derivation (or guile (default-guile))
  272. system #:graft? #f)))
  273. (gexp-&gt;derivation name build
  274. #:system system
  275. #:target #f
  276. #:graft? #f
  277. #:substitutable? substitutable?
  278. #:guile-for-build guile)))</code></pre><p>Finally we define our build system which the package definitions can reference.</p><pre><code class="language-scheme">(define zephyr-build-system
  279. (build-system
  280. (name 'zephyr)
  281. (description &quot;The standard Zephyr build system&quot;)
  282. (lower lower)))</code></pre><p>Easy right?</p><h1>Build Side</h1><p>The build side is not as complex as you might initially expect.
  283. Our build system is almost exactly the same as the CMake build system
  284. except our configure phase passes different values to CMake.
  285. Our job is much easier.</p><h3>Locating Modules</h3><p>Zephyr CMake requires the zephyr <em>modules</em> which are needed for the
  286. build to be supplied on the command line.
  287. Unfortunately for us, the
  288. <a href="https://docs.zephyrproject.org/3.1.0/develop/application/index.html#important-build-vars">documentation</a>
  289. is wrong, and the <code>ZEPHYR_MODULES</code> environment variable is not honored.
  290. Thus we must implement some other solution for locating modules, until
  291. this that is fixed.</p><p><strong>Input Scanning</strong> - Lucky for us we are keeping detailed information
  292. about our dependencies. It is a simple matter to write a file tree
  293. walker which collects all the zephyr modules in our inputs.</p><pre><code class="language-scheme">(define* (find-zephyr-modules directories)
  294. &quot;Return the list of directories containing zephyr/module.yml found
  295. under DIRECTORY, recursively. Return the empty list if DIRECTORY is
  296. not accessible.&quot;
  297. (define (module-directory file)
  298. (dirname (dirname file)))
  299. (define (enter? name stat result)
  300. ;; Skip version control directories.
  301. ;; Shouldn't be in the store but you never know.
  302. (not (member (basename name) '(&quot;.git&quot; &quot;.svn&quot; &quot;CVS&quot;))))
  303. (define (leaf name stat result)
  304. ;; Add module root directory to results
  305. (if (and (string= &quot;module.yml&quot; (basename name))
  306. (string= &quot;zephyr&quot; (basename (dirname name))))
  307. (cons (module-directory name) result)
  308. result))
  309. (define (down name stat result) result)
  310. (define (up name stat result) result)
  311. (define (skip name stat result) result)
  312. (define (find-modules directory)
  313. (file-system-fold enter? leaf down up skip error
  314. '() (canonicalize-path directory)))
  315. (append-map find-modules directories))
  316. (define (zephyr-modules-cmake-argument modules)
  317. &quot;Return a proper CMake list from MODULES, a list of filepaths&quot;
  318. (format #f &quot;-DZEPHYR_MODULES='~{~a~^;~}'&quot; modules))
  319. </code></pre><p>Here are two functions. The first one <code>find-zephyr-modules</code> walks a
  320. list of directories (package inputs) and returns a list of every module it finds.
  321. The second one is just for syntactic convenience when writing the CMake invokation.
  322. This is also slightly more robust than West's module discovery, because it allows for
  323. a single repository to provide multiple modules which are not <em>technically</em> required
  324. to be at the top level.</p><p>From here we just need to provide alternate implementations of <code>configure</code> and <code>install</code>.</p><pre><code class="language-scheme">(define* (configure #:key outputs (configure-flags '())
  325. inputs (out-of-source? #t)
  326. build-type
  327. #:allow-other-keys)
  328. &quot;Configure the given package.&quot;
  329. (let* ((out (assoc-ref outputs &quot;out&quot;))
  330. (abs-srcdir (getcwd))
  331. (srcdir (if out-of-source?
  332. (string-append &quot;../&quot; (basename abs-srcdir))
  333. &quot;.&quot;)))
  334. (format #t &quot;source directory: ~s (relative from build: ~s)~%&quot;
  335. abs-srcdir srcdir)
  336. (when out-of-source?
  337. (mkdir &quot;../build&quot;)
  338. (chdir &quot;../build&quot;))
  339. (format #t &quot;build directory: ~s~%&quot; (getcwd))
  340. ;; this is required because zephyr tries to optimize
  341. ;; future calls to the build scripts by keep a cache.
  342. (setenv &quot;XDG_CACHE_HOME&quot; (getcwd))
  343. (let ((args `(,srcdir
  344. ,@(if build-type
  345. (list (string-append &quot;-DCMAKE_BUILD_TYPE=&quot;
  346. build-type))
  347. '())
  348. ;; enable verbose output from builds
  349. &quot;-DCMAKE_VERBOSE_MAKEFILE=ON&quot;
  350. ,(zephyr-modules-cmake-argument
  351. (find-zephyr-modules (map cdr inputs)))
  352. ,@configure-flags)))
  353. (format #t &quot;running 'cmake' with arguments ~s~%&quot; args)
  354. (apply invoke &quot;cmake&quot; args))))
  355. (define* (install #:key outputs #:allow-other-keys)
  356. (let* ((out (string-append (assoc-ref outputs &quot;out&quot;) &quot;/lib/firmware&quot;))
  357. (dbg (string-append (assoc-ref outputs &quot;debug&quot;) &quot;/share/zephyr&quot;)))
  358. (mkdir-p out)
  359. (mkdir-p dbg)
  360. (copy-file &quot;zephyr/.config&quot; (string-append dbg &quot;/config&quot;))
  361. (copy-file &quot;zephyr/zephyr.map&quot; (string-append dbg &quot;/zephyr.map&quot;))
  362. (copy-file &quot;zephyr/zephyr.elf&quot; (string-append out &quot;/zephyr.elf&quot;))
  363. (copy-file &quot;zephyr/zephyr.bin&quot; (string-append out &quot;/zephyr.bin&quot;))))
  364. ;; Define new standard-phases
  365. (define %standard-phases
  366. (modify-phases cmake:%standard-phases
  367. (replace 'configure configure)
  368. (replace 'install install)))
  369. ;; Call cmake build with our new phases
  370. (define* (zephyr-build #:key inputs (phases %standard-phases)
  371. #:allow-other-keys #:rest args)
  372. (apply cmake:cmake-build #:inputs inputs #:phases phases args))</code></pre><p>One thing to note is the &quot;debug&quot; output. This exists so we don't
  373. retain references to our build environment and make the file system
  374. closure huge. If you put all of the build outputs in the same store
  375. path, then the deployment closure will grow from 2MB to 833MB.</p><h1>Defining Zephyr Packages</h1><p>Now that we have a proper build system, it's time to define some packages!</p><h3>Zephyr Base</h3><p>Zephyr base contains the Zephyr source code. It is equivalent (in my mind
  376. anyway) to the linux kernel, in that packages' definitions' specifications,
  377. which target the linux kernel, can be minimal.</p><p>The selection of operating system actually comes from the toolchain. For
  378. example, we build linux packages with the <a href="https://www.gnu.org/software/autoconf/manual/autoconf-2.65/html_node/Specifying-Target-Triplets.html">gnu
  379. triplet</a>.
  380. When we select the <code>arm-linux-gnueabihf,</code> we are specifying our operating system.</p><p>It is the same for Zephyr. When we build for zephyr we use the <code>arm-zephyr-eabi</code>
  381. toolchain. However, unlike linux applications, zephyr applications are
  382. embedded firmware images and are generally statically linked.
  383. Thus this package just consists of its source code and is not compiled directly.
  384. We cannot compile it now because applications/modules provide the required Kconfig
  385. options.</p><pre><code class="language-scheme">(define-public zephyr
  386. (let ((version &quot;3.1.0&quot;)
  387. (commit &quot;zephyr-v3.1.0&quot;))
  388. (package
  389. (name &quot;zephyr&quot;)
  390. (version version)
  391. (home-page &quot;https://zephyrproject.org&quot;)
  392. (source (origin (method git-fetch)
  393. (uri (git-reference
  394. (url &quot;https://github.com/zephyrproject-rtos/zephyr&quot;)
  395. (commit commit)))
  396. (file-name (git-file-name name version))
  397. (sha256
  398. (base32 &quot;1yl5y9757xc3l037k3g1dynispv6j5zqfnzrhsqh9cx4qzd485lx&quot;))
  399. (patches
  400. ;; this patch makes this package work in a symlinked profile
  401. (search-patches &quot;zephyr-3.1-linker-gen-abs-path.patch&quot;))))
  402. (build-system copy-build-system)
  403. (arguments
  404. `(#:install-plan
  405. '((&quot;.&quot; &quot;zephyr-workspace/zephyr&quot;))
  406. #:phases
  407. (modify-phases %standard-phases
  408. (add-after 'unpack 'patch-cmake-scripts
  409. (lambda* _
  410. (format #t &quot;~a~&amp;&quot; (getcwd))
  411. ;; Some cmake scripts assume the presence of a
  412. ;; git repository in the source directory.
  413. ;; We will just hard-code that information now
  414. (substitute* &quot;CMakeLists.txt&quot;
  415. ((&quot;if\\(DEFINED BUILD_VERSION\\)&quot; all)
  416. (format #f &quot;set(BUILD_VERSION \&quot;~a-~a\&quot;)~&amp;~a&quot;
  417. ,version ,commit all))))))))
  418. (propagated-inputs
  419. (list python-3
  420. python-pyelftools
  421. python-pykwalify
  422. python-pyyaml
  423. python-packaging))
  424. (native-search-paths
  425. (list (search-path-specification
  426. (variable &quot;ZEPHYR_BASE&quot;)
  427. (files '(&quot;zephyr-workspace/zephyr&quot;)))))
  428. (synopsis &quot;Source code for zephyr rtos&quot;)
  429. (description &quot;Zephyr rtos source code.&quot;)
  430. (license license:apsl2))))
  431. (define-public zephyr-3.2.0-rc3
  432. (package (inherit zephyr)
  433. (version &quot;3.2.0-rc3&quot;)
  434. (source (origin (method git-fetch)
  435. (uri (git-reference
  436. (url &quot;https://github.com/zephyrproject-rtos/zephyr&quot;)
  437. (commit &quot;v3.2.0-rc3&quot;)))
  438. (file-name (git-file-name &quot;zephyr&quot; version))
  439. (sha256
  440. (base32 &quot;06ksd9zj4j19jq0zg3lms13jx0gxzjc41433zgb91cnd2cqmn5cb&quot;))
  441. (patches
  442. (search-patches &quot;zephyr-3.1-linker-gen-abs-path.patch&quot;))))))</code></pre><p>Here we use the <code>copy-build-system</code> which takes a list of source destination
  443. pairs. In our case, we just copy everything to the output directory, but not
  444. before patching some files to accomodate our special environment.</p><p>While developing this I wanted to test some toolchain/board features
  445. on the latest release of Zephyr. I included an example of that package
  446. definition to show how we can easily accomodate side by side package
  447. variants and experiment without breaking anything.</p><h3>Modules</h3><p>It's finally time to define some firmware!
  448. Zephyr packages some examples in <code>$ZEPHYR_BASE/samples</code> including a
  449. basic hello world. The k64 development board is already supported
  450. by Zephyr so building the example is trivial.</p><p>In order to actually target the k64 we need to two modules, the NXP
  451. hardware abstraction layer, and CMSIS.
  452. Looking at <code>$ZEPHYR_BASE/west.yml</code> we can see the repositories
  453. and commits which contain these modules. This is how West does
  454. dependency management.</p><p>Defining these packages is not so bad (see footnote 1).</p><pre><code class="language-scheme">(define-public hal-cmsis
  455. (package
  456. (name &quot;hal-cmsis&quot;)
  457. (version &quot;5.8.0&quot;)
  458. (home-page &quot;https://developer.arm.com/tools-and-software/embedded/cmsis&quot;)
  459. (source (origin
  460. (method git-fetch)
  461. (uri (git-reference
  462. (url &quot;https://github.com/zephyrproject-rtos/cmsis&quot;)
  463. (commit &quot;093de61c2a7d12dc9253daf8692f61f793a9254a&quot;)))
  464. (file-name (git-file-name name version))
  465. (sha256
  466. (base32 &quot;0f7cipnwllna7iknsnz273jkvrly16yr6wm4y2018i6njpqh67wi&quot;))))
  467. (build-system zephyr-module-build-system)
  468. (arguments `(#:workspace-path &quot;/modules/hal/cmsis&quot;))
  469. (synopsis &quot;Zephyr module providing the Common Microcontroller
  470. Software Interface Standard&quot;)
  471. (description &quot;Zephyr module providing the Common Microcontroller
  472. Software Interface Standard&quot;)
  473. (license license:apsl2)))
  474. (define-public hal-nxp
  475. (package
  476. (name &quot;hal-nxp&quot;)
  477. (version &quot;3.1.0&quot;)
  478. (home-page &quot;https://nxp.com&quot;)
  479. (source (origin
  480. (method git-fetch)
  481. (uri (git-reference
  482. (url &quot;https://github.com/zephyrproject-rtos/hal_nxp&quot;)
  483. (commit &quot;708c95825b0d5279620935a1356299fff5dfbc6e&quot;)))
  484. (file-name (git-file-name name version))
  485. (sha256
  486. (base32 &quot;1c0i26bpk6cyhr1q4af183jclfmxshv4d15i7k3cz7brzb12m8q1&quot;))))
  487. (build-system zephyr-module-build-system)
  488. (arguments `(#:workspace-path &quot;/modules/hal/nxp&quot;))
  489. (native-search-paths
  490. (list (search-path-specification
  491. (variable &quot;ZEPHYR_MODULES&quot;)
  492. (files `(,(string-append %zephyr-workspace-name module-path)))
  493. (separator &quot;;&quot;))))
  494. (synopsis &quot;Zephyr module for NXP Hardware Abstraction Layer&quot;)
  495. (description &quot;Provides sources for NXP HAL zephyr module&quot;)
  496. (license license:bsd-3)))</code></pre><p>With these two modules defined we can write <code>zephyr-hello-world-frdm-k64f</code>.</p><h3>Hello world</h3><pre><code class="language-scheme">(define-public zephyr-hello-world-frdm-k64f
  497. (package
  498. (name &quot;zephyr-hello-world-frdm-k64f&quot;)
  499. (version (package-version zephyr))
  500. (home-page &quot;https://zephyrproject.org&quot;)
  501. (source (file-append (package-source zephyr)
  502. &quot;/samples/hello_world&quot;))
  503. (build-system zephyr-build-system)
  504. (arguments
  505. '(#:configure-flags '(&quot;-DBOARD=frdm_k64f&quot;)))
  506. (outputs '(&quot;out&quot; &quot;debug&quot;))
  507. (inputs
  508. (list hal-cmsis
  509. hal-nxp))
  510. (synopsis &quot;Hello world example from Zephyr Project&quot;)
  511. (description &quot;Sample package for zephyr project&quot;)
  512. (license license:apsl2)))</code></pre><h3>Building</h3><p>Our above definition can be built using the following:
  513. When testing package definitions, I use the <code>-L</code> flag to point to the local
  514. repository to load the new packages. I will be omitting that flag as
  515. if I had ~guix pull~ed successfully from <a href="https://github.com/paperclip4465/guix-zephyr">guix-zephyr</a>.</p><pre><code class="language-sh">guix build zephyr-hello-world-frdm-k64f
  516. /gnu/store/...-zephyr-hello-world-frdm-k64f-3.1.0-debug
  517. /gnu/store/...-zephyr-hello-world-frdm-k64f-3.1.0</code></pre><p>This actually doesn't fully test our toolchain. The hello world
  518. example, by default, will use a zephyr provided
  519. minimal implementation of the C library and will not link against
  520. newlib.</p><pre><code class="language-scheme">(define-public zephyr-hello-world-newlib-frdm-k64f
  521. (package
  522. (inherit zephyr-hello-world-frdm-k64f)
  523. (name &quot;zephyr-hello-world-newlib-frdm-k64f&quot;)
  524. (arguments
  525. (substitute-keyword-arguments (package-arguments zephyr-hello-world-frdm-k64f)
  526. ((#:configure-flags flags)
  527. `(append
  528. '(&quot;-DCONFIG_MINIMAL_LIBC=n&quot;
  529. &quot;-DCONFIG_NEWLIB_LIBC=y&quot;)
  530. ,flags))))))
  531. guix build zephyr-hello-world-newlib-frdm-k64f
  532. /gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0-debug
  533. /gnu/store/...-zephyr-hello-world-newlib-frdm-k64f-3.1.0</code></pre><p>Woohoo!</p><h1>Further Musings</h1><p>One thing I learned while going through the pains of getting this
  534. working is that even though the components are &quot;modular&quot; there is
  535. still a lot rigid interdependencies, especially on the zephyr base.
  536. Just having two versions of Zephyr in the code base made component
  537. composition fragile.
  538. Modules rely on specific features from the kernel.
  539. This is hidden from developers normally by west automagically walking
  540. the `west.yml` of all of the declared dependencies recursively to
  541. discover the graph.</p><p>While there are many benefits to a modularized build system, a monolithic build
  542. system, like Zephyr, does have many benefits too.</p><p>Part of the problem comes from the domain itself. If you really want to be able
  543. to target the most resource constrained systems and deal with the &quot;industrial
  544. mess&quot; that comes from every board being unique, you have to be as generic and
  545. flexible as possible, which is hard in a guix-like modular build system.</p><p>Superficially the problem is solved in the same way Linux solved it, using
  546. device trees and having a very stable userland interface. However, unlike Linux
  547. where the device tree is compiled to a binary blob and interpreted by drivers at
  548. runtime, Zephyr device trees are compiled to a <code>.h</code> file and are mostly
  549. interpreted by the C pre-processor using an elaborate set of macros.</p><p>It goes beyond simply abstracting the hardware using clever hacks.
  550. Zephyr applications (and any zephyr module) can also introduce new
  551. &quot;kernel&quot; code, configuration options, and even linker script fragments
  552. at build time.
  553. Essentially the Zephyr CMake build system acts like a reverse ld.
  554. Instead of linking libraries after compilation, it discovers these
  555. things before gcc is ever invoked and provides additional code
  556. generation steps.</p><p>Zephyr does not have a stable &quot;userland&quot; interface for the same
  557. reason Linux does not have a stable &quot;kernel module&quot; interface.
  558. Because Zephyr applications are so tightly coupled to the hardware
  559. they run on it is not uncommon to bypass Zephyr utilities and directly
  560. touch hardware and memory.</p><p>In this way they are more related to kernel modules
  561. than userspace applications such as GNU Hello.</p><p>Perhaps there is a lispy way to track several zephyr releases without reducing
  562. the ability to freely modify components in the usual ways...I invite you dear
  563. reader to developer code to explore that possibility.</p><p>It is use-able for Guix anyway.</p><h1>Why a Special Build System? Why not <code>--target=frdm_k64f</code>?</h1><p>That is a fair question!
  564. At first glance you might imagine the following incantation:</p><pre><code class="language-sh">guix build --target=arm-zephyr-eabi hello</code></pre><p>The problem with calling the toolchain directly is that the architecture
  565. is specified by the board selection. It is not generally useful to
  566. compile a board to a different architecture.</p><p>Perhaps maybe something like this then.</p><pre><code class="language-sh">guix build --target=frdm_k64f hello</code></pre><p>The above command tells GNU hello to link against arm-zephyr-newlib and run on
  567. a specific board. The problem is that while this <em>may</em> work for GNU Hello, it
  568. will not work for anything, which requires inputs that are discovered by normal
  569. methods. Only packages which target zephyr explicitly could benefit from such an
  570. interface, and at that point, you may as well record which board is being targeted
  571. in the package definition.</p><p>In general not all zephyr applications can run on every board zephyr
  572. can run on, so the usefulness of the above command is dubious.</p><p>I think if you have firmware which targets multiple boards it is
  573. better to define a package for every board. It is likely every board
  574. will require special configuration flags anyway.</p><h1>Conclusion</h1><p>Zephyr has a very complex build process which can be difficult to
  575. understand and frustrating to set up.</p><p>Using Guix to define firmware packages makes these problems disappear.
  576. It is trivial to create a channel which contains all of the quirks of
  577. your platform and share it with a team or student.</p><p>Packages defined this way serve as a reproducible starting point for
  578. future hardware revisions and variations.</p><h1>Footnotes</h1><ol><li>I also made a <code>zephyr-module-build-system</code> as well which is just the
  579. <code>copy-build-system</code> that mimics the default zephyr workspace layout as provided
  580. by west. This way we do not need to provide the same install-plan for every
  581. module we package. However as I use the <code>copy-build-system</code> more often, it
  582. doesn't really provide much over just using the copy-build system.</li></ol></div></article></section></main><footer><p>© 2020 Joshua Branson. The text on this site is free culture under the Creative Commons Attribution Share-Alike 4.0 International license.</p><p>This website is build with Haunt, a static site generator written in Guile Scheme. Source code is <a href="https://notabug.org/jbranso/gnucode.me">available.</a></p><p>The color theme of this website is based off of the famous <a href="#3f3f3f" target="_blank">zenburn</a> theme.</p></footer></body>