antioxidant.scm 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. ;;; Antioxidant --- Building Rust without cargo
  2. ;;; Copyright © 2022 Maxime Devos <maximedevos@telenet.be>
  3. ;;;
  4. ;;; This file is part of Antioxidant.
  5. ;;;
  6. ;;; Antioxidant is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; Antioxidant is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (antioxidant)
  19. #:export (find-directly-available-crates
  20. crate-directory extract-crate-name extern-arguments
  21. L-arguments/non-rustc
  22. l-arguments/non-rustc
  23. linker-arguments/non-rustc
  24. *manifest*
  25. L-arguments compile-rust compile-rust-library
  26. compile-rust-binary compile-cargo
  27. read-dependency-environment-variables
  28. determine-crate-type
  29. %standard-antioxidant-phases
  30. %default-crate-type
  31. make-manifest manifest? scm->manifest manifest->scm
  32. manifest-lib
  33. manifest-bin
  34. manifest-bench
  35. manifest-example
  36. manifest-test
  37. manifest-features
  38. manifest-dependencies
  39. manifest-dev-dependencies
  40. manifest-build-dependencies
  41. manifest-target-specific
  42. crate-mapping?
  43. make-crate-mapping
  44. crate-mapping-dependency-name
  45. crate-mapping-local-name)
  46. #:use-module (guix build utils)
  47. #:use-module (guix build gnu-build-system)
  48. #:use-module (rnrs records syntactic)
  49. #:use-module (srfi srfi-1)
  50. #:use-module (srfi srfi-26)
  51. #:use-module (srfi srfi-71)
  52. #:use-module (ice-9 match)
  53. #:use-module (ice-9 string-fun)
  54. #:use-module (ice-9 textual-ports)
  55. #:use-module (json)
  56. #:declarative? #false) ;; allow @@ if required
  57. ;; The default crate type (TODO: switch to dylib?)
  58. ;; TODO: why rlib? Because that works. Maybe dylib works too?
  59. (define %default-crate-type "rlib")
  60. ;;;
  61. ;;; Reading Cargo.toml files.
  62. ;;;
  63. (define (or-constant constant)
  64. (lambda (proc)
  65. (lambda (foo)
  66. (if (unspecified? foo)
  67. constant
  68. (proc foo)))))
  69. (define or-false (or-constant #false))
  70. (define or-empty (or-constant '()))
  71. (define or-false* ((or-constant #false) identity))
  72. (define or-true* ((or-constant #true) identity))
  73. (define or-emptystring* ((or-constant "") identity))
  74. ;; rust-libc does not compile with edition=2018
  75. (define %default-edition "2015")
  76. (define or-default-edition* ((or-constant %default-edition) identity))
  77. (define-json-mapping <package> make-package package?
  78. %json->package <=> %package->json <=> scm->package <=> package->scm
  79. (autobins package-autobins "autobins" or-true*) ; boolean
  80. (autoexamples package-autoexamples "autoexamples" or-true*) ; boolean
  81. (autotests package-autotests "autotests" or-true*) ; boolean
  82. (autobenches package-autobenches "autobenches" or-true*) ; boolean
  83. (version package-version "version" or-emptystring*) ; string
  84. (authors package-authors "authors" (or-empty vector->list)) ; vector of strings
  85. (categories package-categories "categories" (or-empty vector->list)) ; vector of strings
  86. (name package-name) ; string
  87. (description package-description "description" or-emptystring*) ; string
  88. (homepage package-homepage "homepage" or-emptystring*) ; string
  89. (repository package-repository "repository" or-emptystring*) ; string
  90. (license package-license "license" or-emptystring*) ; string
  91. (license-file package-license-file "license-file" or-emptystring*) ; string
  92. (edition package-edition "edition" or-default-edition*) ; string
  93. (build package-build "build" or-false*)
  94. (links package-links "links" or-false*)) ; string, despite the s suffix
  95. ;; TODO: not yet used. Maybe in the future we could check for
  96. ;; version incompatibilities?
  97. (define-json-mapping <dependency> make-dependency dependency?
  98. %json->dependency <=> %package->dependency <=> scm->dependency <=> package->dependency
  99. ;; 'name' is the name of the crate, inside the current Rust project.
  100. ;; By default, the name inside the crate is the name ooutside the crate.
  101. ;; However, a crate can choose to use a crate that names itself 'foo'
  102. ;; but use it as-if it was named 'bar', by setting 'name' to "bar"
  103. ;; and 'package' to "foo".
  104. ;;
  105. ;; 'name' is not actually part of the JSON / TOML.
  106. (name dependency-name) ; string
  107. (package dependency-package "package" or-false*) ; string | #false
  108. (optional %dependency-optional) ; boolean
  109. (path %dependency-path) ; string | #false
  110. (version %dependency-version) ; string | #false
  111. (git %dependency-git) ; string | #false
  112. (branch %dependency-branch) ; string | #false
  113. (default-features %dependency-default-features) ; boolean
  114. (registry %dependency-registry)) ; string | #false
  115. (define (scm->dependency-list scm)
  116. (define f
  117. (match-lambda
  118. ((key . value)
  119. (match value
  120. ((? string? version)
  121. (scm->dependency `(("name" . ,key) ("version" . ,version))))
  122. ((? list?) (scm->dependency `(("name" . ,key) ,@value)))))))
  123. (map f scm))
  124. ;;
  125. ;; <https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target>
  126. ;;
  127. ;; For a [lib], [[bin]], [[example]], [[test]] or [[bench]] section.
  128. ;;
  129. (define-json-mapping <target> make-target target?
  130. %json->target <=> %target->json <=> scm->target <=> target->scm
  131. (name target-name "name" or-false*)
  132. (path target-path "path" or-false*)
  133. (test %target-test)
  134. (doctest %target-doctest)
  135. (bench %target-bench)
  136. (doc %target-doc)
  137. (plugin %target-plugin)
  138. (proc-macro %target-proc-macro)
  139. (proc_macro %target-proc_macro)
  140. (harness %target-harness)
  141. (edition target-edition "edition" or-false*)
  142. (crate-type target-crate-type
  143. "crate-type"
  144. ((or-constant (list %default-crate-type))
  145. (lambda (x)
  146. (if (string? x)
  147. (list x)
  148. (vector->list x)))))
  149. ;; NA for [lib]
  150. (required-features target-required-features "required-features"
  151. (or-empty vector->list)))
  152. (define (target-proc-macro target)
  153. ;; TODO: which one is it? (For rust-derive-arbitrary,
  154. ;; it is proc_macro)
  155. (match (list (%target-proc-macro target) (%target-proc_macro target))
  156. (((? boolean? x) _) x)
  157. (((? unspecified?) (? boolean? x)) x)
  158. (((? unspecified?) (? unspecified?)) #false)))
  159. (define (scm->target-list s)
  160. (map scm->target (vector->list s)))
  161. (define-json-mapping <target-specific> make-target-specific? target-specific?
  162. %json->target-specific <=> %manifest->target-specific <=> scm->target-specific <=> target-specific->scm
  163. (target %target-specific-target) ; string, not actually part of the json
  164. (dependencies target-specific-dependencies "dependencies" (or-empty scm->dependency-list))
  165. ;; For tests, examples and benchmarks
  166. (dev-dependencies target-specific-dev-dependencies "dev-dependencies" (or-empty scm->dependency-list))
  167. ;; For build scripts
  168. (build-dependencies target-specific-build-dependencies "build-dependencies" (or-empty scm->dependency-list)))
  169. (define-json-mapping <manifest> make-manifest manifest?
  170. %json->manifest <=> %manifest->json <=> scm->manifest <=> manifest->scm
  171. (package manifest-package "package" scm->package)
  172. (lib manifest-lib "lib" (or-false scm->target))
  173. (bin manifest-bin "bin" (or-empty scm->target-list))
  174. (bench manifest-bench "bench" (or-empty scm->target-list))
  175. (example manifest-example "example" (or-empty scm->target-list))
  176. (test manifest-test "test" (or-empty scm->target-list))
  177. (features manifest-features "features" (or-empty identity))
  178. (dependencies manifest-dependencies "dependencies" (or-empty scm->dependency-list))
  179. ;; For tests, examples and benchmarks
  180. (dev-dependencies manifest-dev-dependencies "dev-dependencies" (or-empty scm->dependency-list))
  181. ;; For build scripts
  182. (build-dependencies manifest-build-dependencies "build-dependencies" (or-empty scm->dependency-list))
  183. (target manifest-target-specific "target"
  184. ;; list of <target-specific>
  185. (or-empty
  186. (lambda (s)
  187. (map (match-lambda
  188. ((key . value)
  189. (scm->target-specific
  190. `(("target" . ,key) ,@value))))
  191. s)))))
  192. (define (convert-toml->json from to)
  193. (invoke "python3" "-c"
  194. "import sys, toml, json
  195. here = sys.argv[1]; there = sys.argv[2];
  196. t = toml.load(here);
  197. with open(there, \"w\") as out_file:
  198. json.dump(t, out_file);"
  199. from to))
  200. (define (open-manifest toml json)
  201. (convert-toml->json toml json)
  202. (define parsed
  203. (call-with-input-file json
  204. (lambda (port)
  205. (json->scm port))
  206. #:encoding "UTF-8"))
  207. (scm->manifest parsed))
  208. ;;
  209. ;; State.
  210. ;;
  211. ;; Set in the 'choose-features' phase. Can be extended in later
  212. ;; (package-specific) phases, until the 'make-feature-closure'
  213. ;; (TODO build.rs) phase.
  214. (define *features* '())
  215. (define *configuration* '()) ;; set by 'configure'
  216. (define *extra-arguments* '()) ; likewise (TODO doc)
  217. ;; TODO: inputs/native-inputs distinction
  218. (define *c-libraries* '())
  219. (define *c-library-directories* '())
  220. ;; Initialised by the 'load-manifest' phase.
  221. (define *manifest* #false)
  222. ;; Packages to test when modifying these two procedures:
  223. ;; * rust-clang-sys
  224. ;; * rust-seccomp-sys
  225. ;; * rust-bindgen
  226. ;; * mayb other -sys crates
  227. (define* (add-c-library! library)
  228. "Link the crate to be compiled against C-LIBRARY -- i.e., do the rust
  229. equivalent of adding \"-lLIBRARY ...\" to the invocation of \"gcc\"."
  230. (let ((corrected-library
  231. (cond ((string-suffix? ".so" library) ; happens for rust-jemalloc-sys@0.3
  232. (format #t "note: the build script explicitly included a .so suffix (~a) for the shared library. We cannot pass that to the linker, so the suffix is removed.~%" library)
  233. (string-drop-right library (string-length ".so")))
  234. ((string-suffix? ".a" library) ; not yet encountered in practice
  235. (format #t "note: the build script explicitly included a .a suffix (~a) for the shared library. We cannot pass that to the linker, so the suffix is removed.~%" library)
  236. (string-drop-right library (string-length ".a")))
  237. ;; TODO: .a case?
  238. (#true library))))
  239. (set! *c-libraries* (cons corrected-library *c-libraries*))))
  240. (define* (add-c-library-directory! library-directory)
  241. "Search for non-Rust libraries in LIBRARY-DIRECTORY -- i.e., do the rust
  242. equivalent of adding \"-LLIBRARY_DIRECTORY\" to the invocation of \"gcc\"."
  243. (set! *c-library-directories* (cons library-directory *c-library-directories*)))
  244. ;;
  245. ;; Information on how to use a crate.
  246. ;;
  247. ;; <crate-information> loaded with 'load-crate-information' can be compared with eq?.
  248. ;; By default, it is assumed <crate-information> is loaded with that.
  249. (define-json-mapping <crate-information> make-crate-information crate-information?
  250. json->crate-information <=> crate-information->json <=>
  251. scm->crate-information <=> crate-information->scm
  252. ;; The following two fields are usually but not always the same:
  253. ;; for rust-debug-unreachable, the first in "debug_unreachable"
  254. ;; and the second is "new_debug_unreachable".
  255. (name crate-information-name) ; string, name of the crate (normalised)
  256. (dependency-name crate-information-dependency-name) ; string, name of the crate put as listed in the dependency information
  257. (link crate-information-link) ; string
  258. ;; Where is the crate (as .rlib or .so or such) located in the file system?
  259. ;; (TODO: check that it's absolute)
  260. (location crate-information-location) ; string
  261. ;; Extra libraries to add (as -l arguments) to compile depending crates.
  262. ;; static= prefixes are allowed.
  263. (libraries crate-information-libraries "libraries" vector->list list->vector)
  264. ;; List of directory names to search for the libraries -- without native=
  265. ;; prefixes or such!
  266. ;; TODO: check that they are absolute.
  267. (library-directories crate-information-library-directories "library-directories" vector->list list->vector)
  268. ;; List of file names of the (non-test, non-build, non-dev) dependencies of
  269. ;; this crate -- the file names point to a <crate-information> JSON.
  270. (dependencies crate-information-dependencies "dependencies" vector->list list->vector)
  271. (environment crate-information-environment)) ;; TODO
  272. (define *known-crate-information* (make-hash-table)) ; file name -> <crate-information>
  273. (define *crate-information->file-name* (make-hash-table))
  274. (define (load-crate-information location)
  275. (match (hash-ref *known-crate-information* location)
  276. (#f (let ((parsed
  277. (scm->crate-information
  278. (call-with-input-file location
  279. json->scm
  280. #:encoding "UTF-8"))))
  281. (hash-set! *known-crate-information* location parsed)
  282. (hashq-set! *crate-information->file-name* parsed location)
  283. parsed))
  284. ((? crate-information? info) info)))
  285. (define (crate-information->file-name crate-info)
  286. (or (hashq-ref *crate-information->file-name* crate-info)
  287. (error (pk 'crate-info crate-info "unknown crate info"))))
  288. ;; Crate names are normalised by the constructor.
  289. (define-record-type (<crate-mapping> %make-crate-mapping crate-mapping?)
  290. ;; From which crate package does the crate come? This is usually, but
  291. ;; not always, the same as the name of the crate.
  292. ;; For 'rust-debug-unreachable', this is "new_debug_unreachable".
  293. (fields (immutable dependency-name crate-mapping-dependency-name) ; string
  294. ;; What does the crate that is using this crate
  295. ;; expect as name (for 'extern ...')? If #false,
  296. ;; default to the crate name (for rust-debug-unreachable,
  297. ;; that is "debug_unreachable").
  298. (immutable local-name %crate-mapping-local-name) ; string | #false
  299. ))
  300. (define crate-mapping-local-name
  301. (case-lambda
  302. ((crate-mapping)
  303. (or (%crate-mapping-local-name crate-mapping)
  304. (error "desired name of crate unknown, pass a <crate-information> to elaborate")))
  305. ((crate-mapping crate)
  306. (unless (crate-mapping? crate-mapping)
  307. (error "argument not a <crate-mapping>"))
  308. (unless (crate-information? crate)
  309. (error "argument not a <crate-information>"))
  310. (or (%crate-mapping-local-name crate-mapping)
  311. (crate-information-name crate)))))
  312. (define (make-crate-mapping dependency-name local-name)
  313. (%make-crate-mapping (normalise-crate-name dependency-name)
  314. (and=> local-name normalise-crate-name)))
  315. (define (normalise-crate-name name)
  316. (string-replace-substring name "-" "_"))
  317. (define (crate-name-of-manifest manifest)
  318. "Return the crate name of the crate specified in MANIFEST."
  319. ;; The 'rust-new-debug-unreachable' crate uses the name
  320. ;; 'debug_unreachable' and not 'new_debug_unreachable'.
  321. ;; So when available, use (target-name lib), otherwise
  322. ;; the build of rust-string-cache@0.8.0 fails.
  323. (let ((package (manifest-package *manifest*))
  324. (lib (manifest-lib *manifest*)))
  325. (or (and=> lib target-name)
  326. (normalise-crate-name (package-name package)))))
  327. (define (partition-crates available-crates crate-mappings)
  328. ;; First return value: direct dependencies
  329. ;; Second return value: indirect dependencies (can contain things not in available-crates!)
  330. ;; Third return value: all things in available-crates not in the previous.
  331. ;;
  332. ;; Direct and indirect dependencies can overlap (e.g.: rust-syn@1.0.82)
  333. (define direct
  334. (filter (lambda (crate-information)
  335. (any (cut match? crate-information <>) crate-mappings))
  336. available-crates))
  337. (define (find-indirect from append-to)
  338. (define (f crate-information)
  339. (map load-crate-information
  340. (crate-information-dependencies crate-information)))
  341. (delete-duplicates (append (append-map f from) append-to) eq?))
  342. (let loop ((indirect (find-indirect direct '())))
  343. (let ((next (find-indirect indirect indirect)))
  344. (if (equal? indirect next) ; fixpoint reached
  345. (values direct indirect
  346. (lset-difference eq? available-crates
  347. (lset-union eq? direct indirect)))
  348. (loop next)))))
  349. (define (filter-used-crates available-crates crate-mappings)
  350. (let* ((direct indirect rest (partition-crates available-crates crate-mappings)))
  351. (append direct indirect)))
  352. (define (find-directly-available-crates inputs)
  353. (append-map (match-lambda
  354. ((_ . input)
  355. (let ((dir (string-append input "/lib/guixcrate")))
  356. (if (directory-exists? dir)
  357. (map load-crate-information
  358. (find-files dir "\\.crate-info"))
  359. '()))))
  360. inputs))
  361. (define (crate-directory store-item)
  362. (string-append store-item "/lib/guixcrate"))
  363. (define* (crate-library-destination crate-name type #:key outputs #:allow-other-keys)
  364. (string-append
  365. (crate-directory (or (assoc-ref outputs "lib")
  366. (assoc-ref outputs "out")))
  367. "/lib" crate-name "." type))
  368. (define* (c-library-destination crate-name type #:key outputs #:allow-other-keys)
  369. (string-append
  370. (or (assoc-ref outputs "lib")
  371. (assoc-ref outputs "out"))
  372. "/lib/lib" crate-name "." type)) ; type = ".a" / ".so"
  373. (define (extract-crate-name lib)
  374. (string-drop
  375. (string-drop-right (basename lib)
  376. (cond ((string-suffix? ".rlib" lib)
  377. (string-length ".rlib"))
  378. ((string-suffix? ".so" lib)
  379. (string-length ".so"))
  380. ((string-suffix? ".a" lib)
  381. (string-length ".a"))
  382. (#true
  383. (format #t "Unrecognised: ~a~%" lib))))
  384. (string-length "lib")))
  385. (define (match? crate-information crate-mapping)
  386. (string=? (crate-mapping-dependency-name crate-mapping)
  387. (crate-information-dependency-name crate-information)))
  388. (define (extern-arguments available-crates crate-mappings)
  389. (define (process-mapping crate-mapping)
  390. (define (do crate)
  391. (string-append "--extern=" (crate-mapping-local-name crate-mapping crate)
  392. "=" (crate-information-location crate)))
  393. ;; Search for a matchin crate
  394. (match (filter (cut match? <> crate-mapping) available-crates)
  395. (()
  396. (format (current-error-port)
  397. "warning: ~a not found in the available crates -- this might cause the build to fail!~%"
  398. crate-mapping)
  399. #f)
  400. ((x) (do x))
  401. ((x y . rest)
  402. (format (current-error-port)
  403. "warning: multiple candidates for ~a (~a, ~a) in the available crates -- this will probably cause the build to fail!~%"
  404. crate-mapping x y)
  405. (do x))))
  406. ;; "rustc" will sort out duplicates in crate-mappings (by emitting an error)(?)
  407. (filter-map process-mapping crate-mappings))
  408. (define* (L-arguments available-crates crate-mappings #:optional
  409. (extra-library-directories '()))
  410. (let* ((direct-dependencies indirect-dependencies rest
  411. (partition-crates available-crates crate-mappings))
  412. (indirect-crate->argument
  413. (lambda (crate-information)
  414. (string-append "-Ldependency="
  415. (dirname (crate-information-location crate-information)))))
  416. ;; No need for -Lcrate, as the full file name is passed to --extern=.
  417. (indirect-crate-arguments
  418. (map indirect-crate->argument indirect-dependencies))
  419. (make-Lnative-argument
  420. (lambda (directory)
  421. ;; native means something different in rustc than Guix.
  422. ;; In Rust, 'native' means non-Rust compiled libraries.
  423. (string-append "-Lnative=" directory)))
  424. (make-Lnative-arguments*
  425. (lambda (crate-information)
  426. (map make-Lnative-argument
  427. (crate-information-library-directories crate-information))))
  428. (Lnative-arguments
  429. (append (map make-Lnative-argument extra-library-directories)
  430. ;; Only use crates that are actually (indirectly) requested.
  431. (append-map make-Lnative-arguments*
  432. (append direct-dependencies indirect-dependencies)))))
  433. ;; Delete duplicates to shrink the invocation of 'rustc' a bit.
  434. (append (delete-duplicates Lnative-arguments string=?)
  435. indirect-crate-arguments))) ; shouldn't contain duplicates
  436. (define (configuration-arguments configuration)
  437. (append-map (lambda (cfg)
  438. (list "--cfg" cfg))
  439. configuration))
  440. (define* (l-arguments available-crates crate-mappings #:optional
  441. (extra-nonrust-libraries '()))
  442. ;; Only involve crates that are actually requested.
  443. ;; Result: a list of -lopenssl, -lstatic=ring-test, ..., arguments.
  444. (let* ((used-dependencies (filter-used-crates available-crates crate-mappings))
  445. (library->argument
  446. (lambda (library)
  447. (string-append "-l" library)))
  448. (crate->l-arguments
  449. (lambda (crate-information)
  450. (map library->argument
  451. (crate-information-libraries crate-information)))))
  452. (delete-duplicates ; shrink invocation of 'rustc'
  453. (append (map library->argument extra-nonrust-libraries)
  454. (append-map crate->l-arguments used-dependencies))
  455. string=?)))
  456. ;; TODO: untested, for newsboat
  457. (define* (L-arguments/non-rustc available-crates crate-mappings)
  458. "Return a list of -L arguments to be passed to a compiler like gcc to link
  459. to the crates in CRATE-MAPPINGS."
  460. ;; gcc doesn't make a -Lnative / -Ldependency / -Lcrate distinction
  461. (let* ((used-dependencies (filter-used-crates available-crates crate-mappings))
  462. (make-L-argument
  463. (lambda (directory)
  464. (string-append "-L" directory)))
  465. (compiled-crate-argument ; for linking to the compiled crate itself (.rlib|so|a|...)
  466. (lambda (crate-information)
  467. (make-L-argument
  468. (dirname (crate-information-location crate-information)))))
  469. (compiled-crate-arguments
  470. (map compiled-crate-argument used-dependencies))
  471. (nonrust-library-arguments*
  472. (lambda (crate-information)
  473. (map make-L-argument
  474. (crate-information-library-directories crate-information))))
  475. (nonrust-library-arguments
  476. ;; Only use crates that are actually (indirectly) requested.
  477. (append-map nonrust-library-arguments* used-dependencies)))
  478. ;; Delete duplicates to shrink the invocation of the C compiler a bit.
  479. (delete-duplicates (append compiled-crate-arguments nonrust-library-arguments))))
  480. ;; TODO: likewise untested!
  481. ;; TODO: for cdylib/dylib/staticlib crates, maybe this should include
  482. ;; the crate itself as well in -l?
  483. (define* (l-arguments/non-rustc available-crates crate-mappings)
  484. "Return a list of -l arguments to be passed to a compiler like gcc to link
  485. to the crates in CRATE-MAPPINGS."
  486. (define (derustify argument)
  487. (string-append "-l"
  488. (string-drop argument
  489. (cond ((string-prefix? "-lstatic=" argument)
  490. (string-length "-lstatic="))
  491. ((string-prefix? "-ldylib=" argument)
  492. (string-length "-ldylib="))
  493. ((string-prefix? "-lframework=" argument)
  494. (error "frameworks not supported"))
  495. ((string-prefix? "-l" argument)
  496. (string-length "-l"))
  497. (#true
  498. (pk 'unrecognised argument)
  499. (error "unrecognised library argument"))))))
  500. (delete-duplicates
  501. (map derustify (l-arguments available-crates crate-mappings))))
  502. (define (linker-arguments/non-rustc available-crates crate-mappings)
  503. (append (L-arguments/non-rustc available-crates crate-mappings)
  504. (l-arguments/non-rustc available-crates crate-mappings)))
  505. (define* (compile-rust source destination extra-arguments
  506. #:key inputs native-inputs outputs
  507. target
  508. (rust-metadata "")
  509. (configuration '())
  510. (available-crates '())
  511. (crate-mappings '())
  512. (extra-libraries *c-libraries*)
  513. (extra-library-directories *c-library-directories*)
  514. #:allow-other-keys)
  515. (mkdir-p (dirname destination))
  516. (apply invoke
  517. "rustc" "--verbose"
  518. (string-append "--target=" target)
  519. ;; Cargo adds '--extern=proc_macro' by default,
  520. ;; see <https://github.com/rust-lang/cargo/pull/7700>.
  521. ;; Make sure that it will be used.
  522. "--extern=proc_macro"
  523. "--cap-lints" "warn" ;; ignore #[deny(warnings)], it's too noisy
  524. "-C" "prefer-dynamic" ;; for C dependencies & grafting and such?
  525. "-C" (string-append "metadata=" rust-metadata) ;; two crates with the same name can only be used in the same binary if they have different metadata, so allow changing the metadata.
  526. source "-o" destination
  527. (append (extern-arguments available-crates crate-mappings)
  528. (L-arguments available-crates crate-mappings extra-library-directories)
  529. (configuration-arguments configuration)
  530. (l-arguments available-crates crate-mappings extra-libraries)
  531. extra-arguments)))
  532. (define* (compile-rust-library source destination crate-name extra-arguments
  533. #:key (crate-type %default-crate-type)
  534. (rust-dynamic-library-arguments #f)
  535. #:allow-other-keys
  536. #:rest arguments)
  537. (apply compile-rust source destination
  538. (append (list (string-append "--crate-name=" crate-name)
  539. (string-append "--crate-type=" crate-type))
  540. (if (string=? crate-type "cdylib")
  541. (or rust-dynamic-library-arguments
  542. (error "I don't know what symbols to export or the version of the library, please set #:rust-dynamic-library-arguments"))
  543. '())
  544. (if (string=? crate-type "dylib") ; TODO: untested!
  545. (or rust-dynamic-library-arguments '())
  546. '())
  547. extra-arguments)
  548. arguments))
  549. (define* (compile-rust-binary source destination extra-arguments
  550. #:key outputs #:allow-other-keys
  551. #:rest arguments)
  552. (apply compile-rust source destination
  553. (append (list "--crate-type=bin")
  554. extra-arguments)
  555. arguments))
  556. ;;;
  557. ;;; Features.
  558. ;;;
  559. (define (features-closure features features-section)
  560. "Include features and the features implied by those features and so on."
  561. (define new-features
  562. (delete-duplicates
  563. ;; lists are not sets, and the order is irrelevant here, so
  564. ;; pick some fixed arbitrary order.
  565. (sort-list
  566. (append-map (lambda (feature)
  567. (define extra
  568. (append
  569. (vector->list
  570. (or (assoc-ref features-section feature) #()))
  571. ;; "package-name/feature-name" is used for enabling
  572. ;; optional dependencies. Apparently, when enabling
  573. ;; optional dependencies, some crates expect the
  574. ;; "package-name" feature to be enabled as well?
  575. ;; (at least rust-pkcs1@0.3.3)
  576. (match (string-index feature #\/)
  577. ((? integer? k)
  578. (list (substring feature 0 k)))
  579. (#false '()))))
  580. (cons feature extra))
  581. features)
  582. string<?)))
  583. (if (equal? features new-features)
  584. ;; fixpoint has been reached
  585. features
  586. (features-closure new-features features-section)))
  587. (define (feature->config feature)
  588. ;; TODO: escapes?
  589. (string-append "feature=\"" feature "\""))
  590. (define* (choose-features #:key (features '("default")) #:allow-other-keys)
  591. "Initialise *features* according to #:features. By default, this enables
  592. the \"default\" feature, and the later 'make-feature-closure' will enable all
  593. default features implied by the \"default\" feature."
  594. (define maybe-car
  595. (match-lambda
  596. (("nightly" . _) #false) ; unlikely to work in Guix, e.g. rust-lock-api@0.4
  597. (("unstable" . _) #false) ; likewise, e.g. rust-fallible-collections@0.4.2
  598. ((x . y) x)))
  599. (match (list (->bool (member "default" features))
  600. (->bool (assoc "default" (manifest-features *manifest*))))
  601. ((#t #f)
  602. ;; See: https://doc.rust-lang.org/cargo/reference/features.html,
  603. ;; ‘the default feature’.
  604. (format #t "The default features are requested but the defaults are not
  605. chosen, enabling all features like Cargo does (except nightly).~%")
  606. (set! *features* (append (filter-map maybe-car (manifest-features *manifest*))
  607. features
  608. *features*)))
  609. ((#f _)
  610. (format #t "warning: not enabling the default features!~%")
  611. (format #t "Using the features ~a and their implied features.~%" features)
  612. (set! *features* (append features *features*)))
  613. (_
  614. (format #t "Using the features ~a and their implied features.~%" features)
  615. (set! *features* (append features *features*))))
  616. (set! *features* (delete-duplicates *features*)))
  617. (define (make-features-closure . _)
  618. (set! *features* (features-closure *features* (manifest-features *manifest*)))
  619. (format #t "The following features will be used: ~a~%." *features*))
  620. ;; Fake cargo crates that antioxidant doesn't need
  621. (define %rustc-std-workspace-crates
  622. (map normalise-crate-name
  623. '("rustc-std-workspace-std"
  624. "rustc-std-workspace-core"
  625. "rustc-std-workspace-alloc")))
  626. ;; If too many crates are included in --extern, errors like
  627. ;; error[E0659]: `time` is ambiguous (name vs any other name during import resolution)
  628. ;; are possible. Avoid them!
  629. (define* (manifest-all-dependencies manifest #:optional (kinds '(dependency dev build)))
  630. "Return a list of crates that are dependencies, as <crate> records."
  631. ;; For now ignore which target a dependency is for.
  632. (define (the-target-specific-dependencies target-specific)
  633. (append (if (memq 'dependency kinds)
  634. (target-specific-dependencies target-specific)
  635. '())
  636. (if (memq 'dev kinds)
  637. (target-specific-dev-dependencies target-specific)
  638. '())
  639. (if (memq 'build kinds)
  640. (target-specific-build-dependencies target-specific)
  641. '())))
  642. (define dependencies
  643. (append (if (memq 'dependency kinds)
  644. (manifest-dependencies manifest)
  645. '())
  646. (if (memq 'dev kinds)
  647. (manifest-dev-dependencies manifest)
  648. '())
  649. (if (memq 'build kinds)
  650. (manifest-build-dependencies manifest)
  651. '())
  652. (append-map the-target-specific-dependencies
  653. (manifest-target-specific manifest))))
  654. (define (construct-crate dependency)
  655. (make-crate-mapping (or (dependency-package dependency)
  656. (dependency-name dependency))
  657. (and (dependency-package dependency) ; <-- first clause required for rust-new-debug-unreachable / rust-string-cache@0.8.0
  658. (dependency-name dependency))))
  659. (define (fake? mapping) ;; avoid warnings about fake crates being missing
  660. (member (crate-mapping-dependency-name mapping) %rustc-std-workspace-crates))
  661. (filter (negate fake?) (map construct-crate dependencies)))
  662. ;; Some cargo:??? lines from build.rs are ‘propagated’ to dependencies
  663. ;; as environment variables, see
  664. ;; <https://doc.rust-lang.org/cargo/reference/build-script-examples.html>.
  665. (define* (read-dependency-environment-variables
  666. #:key (inputs '())
  667. (native-inputs '())
  668. #:allow-other-keys)
  669. ;; TODO: also for indirect dependencies?
  670. (define (setenv* x y)
  671. (format #t "setting ~a to ~a~%" x y)
  672. (setenv x y))
  673. (define (drop-native=-prefix directory)
  674. ;; Strip native= and all= prefixes from 'directory'
  675. (cond ((string-prefix? "native=" directory)
  676. (string-drop directory (string-length "native=")))
  677. ((string-prefix? "all=" directory)
  678. (string-drop directory (string-length "all=")))
  679. (#t directory)))
  680. (define (do crate-info)
  681. (format #t "setting extra environment variables in ~a~%" crate-info)
  682. (for-each
  683. (match-lambda
  684. ((x . y) (setenv*
  685. (string-replace-substring
  686. (string-upcase
  687. (string-append
  688. "DEP_"
  689. (crate-information-link crate-info)
  690. "_"
  691. x))
  692. "-"
  693. "_")
  694. y)))
  695. (crate-information-environment crate-info)))
  696. (for-each do
  697. (find-directly-available-crates (delete-duplicates (append native-inputs inputs)))))
  698. (define* (save-crate-info link-name saved-settings library-destination
  699. #:key inputs outputs #:allow-other-keys)
  700. (define where (string-append (or (assoc-ref outputs "env")
  701. (assoc-ref outputs "lib")
  702. (assoc-ref outputs "out")) ;; maybe switch the last two?
  703. "/lib/guixcrate/" link-name ".crate-info"))
  704. (define available-crates (find-directly-available-crates inputs))
  705. (define crate-mappings (manifest-all-dependencies *manifest* '(dependency)))
  706. (format #t "Saving crate informtion in ~a~%" where)
  707. (mkdir-p (dirname where))
  708. ;; /tmp/guix-build-... directories won't exist after the build is finished,
  709. ;; so including them is pointless.
  710. (define (directory-without-prefix dir)
  711. (cond ((string-prefix? "native=" dir)
  712. (string-drop dir (string-length "native=")))
  713. ((string-prefix? "all=" dir)
  714. (string-drop dir (string-length "all=")))
  715. (#t dir)))
  716. (define (local-directory? dir)
  717. (string-prefix? (getcwd) (directory-without-prefix dir)))
  718. ;; If the build.rs compiled a C library and linked it into the crate,
  719. ;; then at least for cases known at writing, rustc will link the local
  720. ;; C library into the rlib (rust-sha2-asm@0.6.1), so including them in
  721. ;; -l later is pointless, especially given that they won't be found later.
  722. (define (locally-compiled-c-library? foo)
  723. (let* ((name (if (string-prefix? "static=" foo)
  724. (string-drop foo (string-length "static="))
  725. foo))
  726. (basename (format #f "lib~a.a" name)))
  727. (define (match? c-library-directory)
  728. (and (local-directory? c-library-directory)
  729. (file-exists? (in-vicinity
  730. (directory-without-prefix c-library-directory)
  731. basename))))
  732. ;; rust-sha2-asm doesn't add the current directory to c-library-directories
  733. ;; even though it adds a static library there.
  734. (any match? (cons (getcwd) *c-library-directories*))))
  735. (define filtered-c-libraries
  736. (filter (negate locally-compiled-c-library?) *c-libraries*))
  737. (define filtered-library-directories
  738. (filter (negate local-directory?) *c-library-directories*))
  739. (call-with-output-file where
  740. (lambda (o)
  741. (scm->json
  742. (crate-information->scm
  743. (make-crate-information (crate-name-of-manifest *manifest*)
  744. ;; TODO: should the dependency name be normalised?
  745. (normalise-crate-name (package-name (manifest-package *manifest*)))
  746. link-name
  747. *library-destination*
  748. filtered-c-libraries
  749. filtered-library-directories
  750. ;; direct dependencies
  751. (map crate-information->file-name
  752. (partition-crates available-crates crate-mappings))
  753. ;; TODO: maybe filter out uninteresting things like
  754. ;; core-rerun-if-changed?
  755. saved-settings))
  756. o))
  757. #:encoding "UTF-8"))
  758. ;; To avoid cluttering the .crate-info and to reduce the number of environment
  759. ;; variables set, exclude these variables which aren't used by dependents.
  760. ;; Not exhaustive.
  761. (define %excluded-keys
  762. ;; 'include' is used by rust-tectonic-engine-bibtex@0.1.1
  763. '("rerun-if-env-changed" "rerun-if-changed" "rustc-link-search" "rustc-link-lib"
  764. "rustc-cfg" "warning"))
  765. (define *save* #false) ;; TODO: less impure
  766. (define* (configure #:key inputs native-inputs target build optimisation-level
  767. #:allow-other-keys #:rest arguments)
  768. (define saved-settings '())
  769. (define (save! key value)
  770. "Add a KEY=VALUE mapping to the saved settings, unless it is excluded
  771. by %excluded-keys."
  772. (unless (member key %excluded-keys)
  773. (set! saved-settings (cons (cons key value) saved-settings))))
  774. (define extra-configuration '()) ; --cfg options, computed by build.rs
  775. (define (handle-line line)
  776. (when (string-prefix? "cargo:" line)
  777. (let* ((rest (string-drop line (string-length "cargo:")))
  778. (=-index (string-index rest #\=)))
  779. (if =-index
  780. (let ((this (substring rest 0 =-index))
  781. (that (substring rest (+ 1 =-index))))
  782. (save! this that))
  783. (begin
  784. (pk 'l rest)
  785. (error "cargo: line doesn't look right, = missing?")))))
  786. (cond ((string-prefix? "cargo:rustc-cfg=" line)
  787. (format #t "Building with --cfg ~a~%" line) ;; todo invalid
  788. (set! extra-configuration
  789. (cons (string-drop line (string-length "cargo:rustc-cfg="))
  790. extra-configuration)))
  791. ;; The rustc-link-lib and rustc-link-search will be added to the <crate-information>.
  792. ((string-prefix? "cargo:rustc-link-lib=" line)
  793. (let ((c-library (string-drop line (string-length "cargo:rustc-link-lib="))))
  794. (format #t "Building with C library ~a~%" c-library)
  795. (add-c-library! c-library)))
  796. ((string-prefix? "cargo:rustc-link-search=" line)
  797. (let ((KIND=PATH (string-drop line (string-length "cargo:rustc-link-search="))))
  798. (cond ((string-prefix? "framework=" KIND=PATH)
  799. (error "framework not yet supported"))
  800. ((string-prefix? "native=" KIND=PATH)
  801. (add-c-library-directory! (string-drop KIND=PATH (string-length "native="))))
  802. ((string-prefix? "all=" KIND=PATH)
  803. ;; Note (Cargo incompatibility?): technically the build.rs could ask us
  804. ;; here to search for crates in some arbitrary directories (instead of
  805. ;; only C-style libraries), but no crate(™) does that (so far ...)
  806. (add-c-library-directory! (string-drop KIND=PATH (string-length "=all"))))
  807. ((or (string-prefix? "crate=" KIND=PATH)
  808. (string-prefix? "dependency=" KIND=PATH))
  809. (error "The build script is not supposed to ask to look into arbitrary locations for crates."))
  810. (#true
  811. (add-c-library-directory! KIND=PATH)))))
  812. ((string-prefix? "cargo:rustc-env=" line)
  813. (putenv (string-drop line (string-length "cargo:rustc-env="))))
  814. ((string-prefix? "cargo:warning=" line)
  815. (format (current-error-port)
  816. "configuration script: warning: ~a~%"
  817. (string-drop line (string-length "cargo:warning="))))
  818. ((or (string-prefix? "cargo:rerun-if-changed=" line)
  819. (string-prefix? "cargo:rerun-if-env-changed=" line))
  820. (values)) ; nothing to do for antioxidant, no need for a warning
  821. ((string-prefix? "cargo:" line)
  822. (pk 'l line)
  823. (format #t "warning: ~a: unrecognised build.rs instruction~%" line)
  824. (format #t "hint: maybe the crate is just saving an environment variable for dependencies, maybe nothing needs to be changed.\n"))
  825. ;; Some build.rs (e.g. the one of rust-pico-sys)
  826. ;; print strings like "TARGET = Some(\"TARGET\")". Maybe
  827. ;; they are just debugging information that can be ignored
  828. ;; by cargo -- err, antioxidant.
  829. (#true
  830. (format #t "info from build.rs: ~a~%" line))))
  831. (setenv "CARGO_MANIFEST_DIR" (getcwd)) ; directory containing the Cargo.toml
  832. (define package (manifest-package *manifest*))
  833. (define build.rs
  834. (or (package-build package)
  835. ;; E.g, rust-proc-macros2 doesn't set 'build'
  836. ;; even though it has a configure script.
  837. (and (file-exists? "build.rs") "build.rs")))
  838. (when build.rs
  839. (format #t "building configuration script~%")
  840. (apply
  841. compile-rust-binary build.rs "configuration-script"
  842. (list (string-append "--edition=" (package-edition package)))
  843. (append arguments
  844. ;; In Cargo, the build script _does not_ have access to dependencies
  845. ;; in 'dependencies' or 'dev-dependencies', only 'build-dependencies',
  846. ;; see
  847. ;; <https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html>.
  848. (list #:crate-mappings (manifest-all-dependencies *manifest* '(build))
  849. #:available-crates (find-directly-available-crates native-inputs)
  850. ;; Build for the machine the configuration script will be run
  851. ;; on.
  852. #:target build ; todo: correct terminology?
  853. #:configuration (map feature->config *features*))))
  854. ;; Expected by rust-const-fn's build.rs
  855. (setenv "OUT_DIR" (getcwd))
  856. ;; Expected by rust-libm's build.rs
  857. (setenv "OPT_LEVEL" (if (number? optimisation-level)
  858. (number->string optimisation-level)
  859. optimisation-level))
  860. ;; Expected by some configuration scripts, e.g. rust-libc
  861. (setenv "RUSTC" (which "rustc"))
  862. ;; This improves error messages
  863. (setenv "RUST_BACKTRACE" "full")
  864. ;; rust-indexmap expectes this to be set (TODO: this is rather ad-hoc)
  865. (setenv "CARGO_FEATURE_STD" "")
  866. (setenv "TARGET" target) ; used by rust-proc-macro2's build.rs
  867. (setenv "HOST" build) ; used by rust-pico-sys
  868. ;; TODO: use pipes
  869. (format #t "running configuration script~%")
  870. (unless (= 0 (system "./configuration-script > .guix-config"))
  871. (error "configuration script failed"))
  872. (call-with-input-file ".guix-config"
  873. (lambda (port)
  874. (let loop ((r (get-line port)))
  875. (match r
  876. ((? string? line) (handle-line line) (loop (get-line port)))
  877. ((? eof-object? line) (values)))))))
  878. (set! *configuration* (append extra-configuration (map feature->config *features*)))
  879. (set! *save*
  880. (lambda (library-destination)
  881. (apply save-crate-info (or (package-links package)
  882. (package-name package))
  883. saved-settings library-destination
  884. arguments)))
  885. (format #t "Building with configuration options: ~a~%" *configuration*))
  886. (define *library-destination* #f)
  887. (define* (determine-crate-type manifest #:key rust-crate-type #:allow-other-keys #:rest arguments)
  888. "Return the crate type to build this rust crate as."
  889. (define lib (manifest-lib manifest))
  890. (cond (rust-crate-type rust-crate-type) ; override
  891. ((not lib) %default-crate-type)
  892. ;; TODO: which one is it? (For rust-derive-arbitrary,
  893. ;; it is proc_macro)
  894. ((target-proc-macro lib) ; proc-macro
  895. "proc-macro")
  896. (#true
  897. (match (target-crate-type lib)
  898. (() (error "There must be at least one crate type."))
  899. ((x) x)
  900. ((? list? rest)
  901. (pk 'types rest 'in manifest)
  902. (error "antioxidant only supports a single crate type, override Cargo.toml with #:rust-crate-type"))))))
  903. (define* (build #:key rust-crate-type inputs #:allow-other-keys #:rest arguments)
  904. "Build the Rust crates (library) described in Cargo.toml."
  905. ;; Tested for: rust-cfg-il, rust-libc (TODO: more)
  906. (let* ((package (manifest-package *manifest*))
  907. (crate-mappings (manifest-all-dependencies *manifest* '(dependency)))
  908. (lib (manifest-lib *manifest*))
  909. (crate-name (crate-name-of-manifest *manifest*))
  910. (edition (package-edition package))
  911. ;; Location of the crate source code to compile.
  912. ;; The default location is src/lib.rs, some packages put
  913. ;; the code elsewhere.
  914. (lib-path (or (and=> lib target-path)
  915. (and (file-exists? "src/lib.rs") "src/lib.rs")))
  916. (crate-type (apply determine-crate-type *manifest* arguments)))
  917. (unless (member crate-type '("bin" "lib" "rlib" "dylib" "cdylib" "staticlib" "proc-macro"))
  918. ;; Note: not all of these crate types have been tested.
  919. (pk 'c crate-type)
  920. (error "unrecognised crate type"))
  921. (when (and (string=? crate-type "staticlib")
  922. (not rust-crate-type))
  923. (error "The Cargo.toml has asked for a staticlib, but Rust staticlibs include all their dependencies (in contrast to C static libraries) and hence don't play well with grafts, so this needs to be confirmed by setting #:rust-crate-type explicitly"))
  924. ;; TODO: implement proper library/binary autodiscovery as described in
  925. ;; <https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery>.
  926. (when lib-path
  927. (set! *library-destination*
  928. (apply (if (member crate-type '("cdylib")) ; TODO: maybe also for 'dylib'?
  929. c-library-destination
  930. crate-library-destination)
  931. crate-name
  932. (cond ((member crate-type '("cdylib" "dylib" "proc-macro"))
  933. "so")
  934. ((member crate-type '("staticlib")) ; used by newsboat-ffi
  935. "a")
  936. ((member crate-type '("rlib" "lib"))
  937. "rlib")
  938. (#true
  939. (pk 'c crate-type)
  940. (error "bogus crate type -- should be unreachable")))
  941. arguments)) ;; TODO: less impure
  942. (*save* *library-destination*)
  943. (apply compile-rust-library lib-path *library-destination*
  944. crate-name
  945. ;; Version of the Rust language (cf. -std=c11)
  946. ;; -- required by rust-proc-macro2
  947. (list (string-append "--edition=" (package-edition package))
  948. ;; Some build.rs put libraries in the current directory
  949. ;; (or, at least, in OUT_DIR or something like that).
  950. ;; TODO: can be done tidier.
  951. ;; TODO: is this still necessary, now we interpret
  952. ;; rustc-link-search and such?
  953. (string-append "-Lnative=" (getcwd)))
  954. #:crate-type crate-type
  955. #:available-crates (find-directly-available-crates inputs)
  956. #:crate-mappings crate-mappings
  957. ;; TODO: does the order matter?
  958. (append arguments (list #:configuration *configuration*))))))
  959. ;; See <https://doc.rust-lang.org/cargo/guide/project-layout.html>
  960. ;; for how source locations are inferred.
  961. (define (infer-binary-source target)
  962. "Guess the Rust source code location of TARGET, a <target> record. If not found,
  963. return false instead."
  964. (define inferred-source0
  965. (and (target-name target)
  966. (format #f "src/bin/~a.rs" (target-name target))
  967. ;; TODO: for 100% paranoia, check that inferred-source0
  968. ;; doesn't contain #\nul, slashes or .. components.
  969. ))
  970. ;; default executable (TODO: is this code path actually ever used?)
  971. (define inferred-source1 "src/main.rs")
  972. (or (target-path target) ; explicit
  973. (and inferred-source0 (file-exists? inferred-source0) inferred-source0)
  974. (and (file-exists? inferred-source1) inferred-source1)))
  975. (define* (build-binaries #:key inputs outputs #:allow-other-keys #:rest arguments)
  976. "Compile the Rust binaries described in Cargo.toml"
  977. (define package (manifest-package *manifest*))
  978. (define files-visited '())
  979. (define (normalize-relative-file-name name)
  980. ;; find-files includes a ./ prefix, but infer-binary-source doesn't.
  981. ;; Make sure ./src/bin/foo.rs and src/bin/foo.rs are treated equally.
  982. (if (string-prefix? "./" name)
  983. (string-drop name 2)
  984. name))
  985. (define (mark-file-visited! file-name)
  986. (set! files-visited (cons (normalize-relative-file-name file-name) files-visited)))
  987. (define (is-file-visited? file-name)
  988. (member (normalize-relative-file-name file-name) files-visited))
  989. (define extern-crates (manifest-all-dependencies *manifest* '(dependency)))
  990. (define (binary-location binary)
  991. (string-append (or (assoc-ref outputs "bin")
  992. (assoc-ref outputs "out"))
  993. "/bin/" binary))
  994. (define* (cb source binary edition)
  995. (apply compile-rust-binary source
  996. (binary-location binary)
  997. (list (string-append "--edition=" edition)
  998. (string-append "-Lnative=" (getcwd)))
  999. ;; A program can use its own crate without declaring it.
  1000. ;; At least, hexyl tries to do so. For a more complicated
  1001. ;; example, see 'rust-xml-rs@0.8.3', which has "xml_rs" as
  1002. ;; package name and "xml" as --extern name.
  1003. #:crate-mappings (cons (make-crate-mapping (package-name package)
  1004. (crate-name-of-manifest *manifest*))
  1005. extern-crates)
  1006. ;; Binaries can use their own crates!
  1007. #:available-crates
  1008. (find-directly-available-crates (append outputs inputs))
  1009. ;; TODO: figure out how to override things
  1010. (append
  1011. arguments
  1012. (list #:configuration *configuration*))))
  1013. ;; TODO: respect required-features.
  1014. (define (compile-bin-target target)
  1015. (define source (infer-binary-source target)) ; can be #false if not found
  1016. ;; Make sure they won't be compiled after the the 'package-autobins'
  1017. ;; below if required features are missing. This is required
  1018. ;; for building rust-multipart.
  1019. (when source
  1020. (mark-file-visited! source))
  1021. (cond ((not (lset<= string=? (target-required-features target) *features*))
  1022. (format #t "not compiling ~a, because the following features are missing: ~a~%"
  1023. target ; we don't care if the source exists when we are not compiling it.
  1024. (lset-difference string=?
  1025. (target-required-features target)
  1026. *features*)))
  1027. ((not source)
  1028. ;; Maybe the file has been removed due to being non-free,
  1029. ;; requiring dependencies not packaged in Guix, or requiring
  1030. ;; a non-stable rust. This skipping used to be required for
  1031. ;; rust-phf-generator back when required-features wasn't expected
  1032. ;; and hence gen_hash_test.rs had to be removed in a phase.
  1033. (format #t "warning: source code of ~a could not be found, skipping.~%" target))
  1034. (#true
  1035. (format #t "Compiling ~a~%" source)
  1036. (cb source (or (target-name target) (package-name package))
  1037. (or (target-edition target) (package-edition package))))))
  1038. (for-each compile-bin-target (manifest-bin *manifest*))
  1039. (when (package-autobins package)
  1040. (when (and (file-exists? "src/main.rs")
  1041. (not (is-file-visited? "src/main.rs")))
  1042. (mark-file-visited! "src/main.rs")
  1043. (cb "src/main.rs" (package-name package) (package-edition package)))
  1044. (for-each ;; TODO: support [[bin]] (TODO: resolved?)
  1045. (lambda (file)
  1046. (when (and (string-suffix? ".rs" file)
  1047. ;; Possibly the binary was already in [[bin]]
  1048. ;; and hence is pointless to compile again.
  1049. ;; Might also be impossible due to missing
  1050. ;; features (see 'compile-bin-target').
  1051. (not (is-file-visited? file)))
  1052. (cb file (string-drop-right (basename file)
  1053. (string-length ".rs"))
  1054. (package-edition package))))
  1055. (find-files "src/bin"))))
  1056. (define* (load-manifest . rest)
  1057. "Parse Cargo.toml and save it in @code{*manifest*}."
  1058. (set! *manifest* (open-manifest "Cargo.toml" "Cargo.json")))
  1059. ;; rust-bzip2-sys has a 0.1.9+1.0.8 version string.
  1060. ;; Presumably CARGO_PKG_VERSION_MAJOR/MINOR/PATCH must be 0, 1, 9.
  1061. ;; TODO: what does PRE mean?
  1062. (define (without-plus version)
  1063. (match (string-split version #\+)
  1064. ((first . rest) first)))
  1065. ;; Set some variables that Cargo can set and that might
  1066. ;; be expected by build.rs. A (full?) list is avialable
  1067. ;; at <https://doc.rust-lang.org/cargo/reference/environment-variables.html>.
  1068. ;; When something does not appear in the Cargo.toml or such, according to
  1069. ;; that documentation, the environment variable needs to be set to the empty
  1070. ;; string.
  1071. (define* (set-platform-independent-manifest-variables
  1072. #:key (cargo-target-directory #false) #:allow-other-keys)
  1073. (define package (manifest-package *manifest*))
  1074. ;; Used by rust-cmake. TODO: actually set the various profile flags,
  1075. ;; optimisation levels, ...
  1076. (setenv "PROFILE" "release")
  1077. (setenv "DEBUG" "true")
  1078. (setenv "NUM_JOBS" (number->string (parallel-job-count)))
  1079. (let ((set-version-environment-variables
  1080. (lambda (major minor patch pre)
  1081. (setenv "CARGO_PKG_VERSION_MAJOR" major)
  1082. (setenv "CARGO_PKG_VERSION_MINOR" minor)
  1083. (setenv "CARGO_PKG_VERSION_PATCH" patch)
  1084. (setenv "CARGO_PKG_VERSION_PRE" pre))))
  1085. (match (string-split (without-plus (package-version package)) #\.)
  1086. ((major minor patch pre . rest) ; rest: unusual (non-existent?), but antioxidant doesn't care
  1087. (set-version-environment-variables major minor patch pre))
  1088. ((major minor patch)
  1089. (set-version-environment-variables major minor patch ""))
  1090. ((major minor)
  1091. (set-version-environment-variables major minor "" ""))
  1092. ((major)
  1093. (set-version-environment-variables major "" "" ""))
  1094. (() ; not set in Cargo.toml
  1095. (set-version-environment-variables "" "" "" ""))))
  1096. (setenv "CARGO_PKG_VERSION" (package-version package))
  1097. (setenv "CARGO_PKG_AUTHORS" (string-join (package-authors package) ":"))
  1098. (setenv "CARGO_PKG_NAME" (package-name package))
  1099. (setenv "CARGO_PKG_DESCRIPTION" (package-description package))
  1100. (setenv "CARGO_PKG_HOMEPAGE" (package-homepage package))
  1101. (setenv "CARGO_PKG_REPOSITORY" (package-repository package))
  1102. (setenv "CARGO_PKG_LICENSE" (package-license package))
  1103. (setenv "CARGO_PKG_LICENSE_FILE" (package-license-file package))
  1104. ;; According to Cargo, this is the directory for all ‘generated artifacts
  1105. ;; and intermediate files’ and defaults to a directory "target" in the working
  1106. ;; directory. However, in Guix, we want to install things in /gnu/store.
  1107. ;; It is also unclear what the file hierarchy is and which artifacts
  1108. ;; should be preserved in the store item and which should be removed.
  1109. ;;
  1110. ;; As such, don't set CARGO_TARGET_DIR by default and instead leave it
  1111. ;; to the packager to decide whether a cwd / store CARGO_TARGET_DIR is
  1112. ;; reasonable and what to preserve / remove.
  1113. ;;
  1114. ;; As an example, rust-cxx-build and newsboat make use of CARGO_TARGET_DIR.
  1115. (when cargo-target-directory
  1116. (let ((cargo-target-directory
  1117. (if (absolute-file-name? cargo-target-directory)
  1118. cargo-target-directory
  1119. (in-vicinity (getcwd) cargo-target-directory))))
  1120. (mkdir-p cargo-target-directory)
  1121. (setenv "CARGO_TARGET_DIR" cargo-target-directory))))
  1122. (define* (set-platform-dependent-variables #:key cargo-env-variables
  1123. #:allow-other-keys)
  1124. "Set environment variables like CARGO_CFG_TARGET_POINTER_WIDTH and
  1125. CARGO_CFG_TARGET_ARCH."
  1126. (for-each (match-lambda ((name . value) (setenv name value)))
  1127. cargo-env-variables)) ; TODO: maybe move more things inside
  1128. ;; Otherwise it looks for TARGET-strip even when compiling natively,
  1129. ;; due to how cross-compilation has been set up.
  1130. (define* (fixed-strip #:key target build #:allow-other-keys #:rest arguments)
  1131. (if (string=? target build)
  1132. (apply (assoc-ref %standard-phases 'strip)
  1133. (append arguments
  1134. (list #:target #false)))
  1135. (apply (assoc-ref %standard-phases 'strip) arguments)))
  1136. (define %standard-antioxidant-phases
  1137. (modify-phases %standard-phases
  1138. ;; TODO: before configure?
  1139. (add-after 'unpack 'make-features-closure make-features-closure)
  1140. (add-after 'unpack 'choose-features choose-features)
  1141. (add-after 'unpack 'read-dependency-environment-variables read-dependency-environment-variables)
  1142. (add-after 'unpack 'set-platform-independent-manifest-variables
  1143. set-platform-independent-manifest-variables)
  1144. (add-after 'unpack 'set-platform-dependent-variables set-platform-dependent-variables)
  1145. (add-after 'unpack 'load-manifest load-manifest)
  1146. (replace 'configure configure)
  1147. (replace 'build build)
  1148. (add-after 'build 'build-binaries build-binaries)
  1149. (replace 'strip fixed-strip)
  1150. (delete 'check) ; TODO
  1151. (delete 'install))) ; TODO?