antioxidant.scm 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855
  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 (call-with-reset-state
  20. capture-state
  21. with-reset-state
  22. load-manifest
  23. find-directly-available-crates
  24. crate-directory extract-crate-name extern-arguments
  25. L-arguments/non-rustc
  26. l-arguments/non-rustc
  27. linker-arguments/non-rustc
  28. *manifest*
  29. L-arguments compile-rust compile-rust-library
  30. compile-rust-binary compile-cargo
  31. read-dependency-environment-variables
  32. determine-crate-type
  33. %standard-antioxidant-phases
  34. %default-crate-type
  35. %default-skipped-integration-tests
  36. normalise-crate-name
  37. make-workspace workspace? scm->workspace workspace->scm
  38. workspace-members
  39. workspace-exclude
  40. workspace-resolver
  41. workspace-default-members
  42. workspace-package
  43. workspace-dependencies
  44. workspace-metadata
  45. open-manifest
  46. make-manifest manifest? scm->manifest manifest->scm
  47. manifest-package
  48. manifest-all-dependencies
  49. manifest-workspace
  50. manifest-lib
  51. manifest-bin
  52. manifest-bench
  53. manifest-example
  54. manifest-test
  55. manifest-features
  56. manifest-dependencies
  57. manifest-dev-dependencies
  58. manifest-build-dependencies
  59. manifest-target-specific
  60. make-package package?
  61. package-name
  62. scm->package package->scm
  63. package-autobins
  64. package-autoexamples
  65. package-autotests
  66. package-autobenches
  67. package-version
  68. package-authors
  69. package-categories
  70. package-name
  71. package-description
  72. package-homepage
  73. package-repository
  74. package-license
  75. package-license-file
  76. package-edition
  77. package-build
  78. package-links
  79. crate-mapping?
  80. make-crate-mapping
  81. crate-mapping-dependency-name
  82. crate-mapping-local-name
  83. elaborate-target
  84. elaborate-target/skip
  85. elaborated-target?
  86. find-rust-binaries
  87. find-rust-tests
  88. compile-binary-target
  89. save-crate-information!
  90. generate-cbindgen-metadata
  91. rust-tests-check
  92. rust-tests-check/xorg)
  93. #:use-module (guix build syscalls)
  94. #:use-module (guix build utils)
  95. #:use-module (guix build gnu-build-system)
  96. #:use-module (rnrs records syntactic)
  97. #:use-module (srfi srfi-1)
  98. #:use-module (srfi srfi-9 gnu)
  99. #:use-module (srfi srfi-26)
  100. #:use-module (srfi srfi-34) ; or is the RNRS preferred?
  101. #:use-module (srfi srfi-35)
  102. #:use-module (srfi srfi-71)
  103. #:use-module (ice-9 control)
  104. #:use-module (ice-9 format)
  105. #:use-module (ice-9 match)
  106. #:use-module (ice-9 string-fun)
  107. #:use-module (ice-9 textual-ports)
  108. #:use-module (json)
  109. #:declarative? #false) ;; allow @@ if required
  110. ;;;
  111. ;;; Workspaces.
  112. ;;;
  113. ;;; Sometimes, Rust libraries or applications have internal dependencies.
  114. ;;; In Cargo, the structure of internal dependencies is declared with
  115. ;;; 'workspaces'.
  116. ;;;
  117. ;;; TODO: WIP.
  118. ;;;
  119. (define (scm->string-list v message)
  120. "Convert the JSON list V of strings into a Scheme list of strings,
  121. and use MESSAGE as an error message in case of typing errors."
  122. (define (oops)
  123. (error message))
  124. (define (check-entry item)
  125. (unless (string? item)
  126. (oops)))
  127. (unless (vector? v)
  128. (oops))
  129. (define result (vector->list v))
  130. (for-each check-entry result)
  131. result)
  132. ;; Upstream documentation:
  133. ;; <https://doc.rust-lang.org/cargo/reference/workspaces.html>
  134. (define-json-mapping <workspace> make-workspace workspace?
  135. %json->workspace <=> %workspace->json <=> scm->workspace <=> workspace->scm
  136. (members workspace-members "members"
  137. (or-empty (cut scm->string-list
  138. <>
  139. "the 'members' field of [workspace] in the manifest\
  140. must be a list of strings"))) ; list of glob patterns
  141. (exclude workspace-exclude "exlude"
  142. (or-empty (cut scm->string-list
  143. <>
  144. "the 'exclude' field of [workspace] in the manifest\
  145. must be a list of strings"))) ; list of glob patterns
  146. (resolver workspace-resolver "resolver") ; unused
  147. (default-members workspace-default-members "members"
  148. (or-empty (cut scm->string-list
  149. <>
  150. "the 'default-members' field of [workspace] in the manifest\
  151. must be a list of strings"))) ; list of file names (directories)
  152. (package workspace-package "package" (or-empty identity)) ; uninterpreted JSON value, to be merged into the JSON of packages
  153. ;; Uninterpreted JSON list, to be merged into the JSON of the 'dependencies'
  154. ;; field of members (and soetimes the dev-dependencies or build-dependencies).
  155. (dependencies workspace-dependencies "dependencies" (or-empty identity))
  156. ;; Uninterpreted JSON list, used by neither Cargo nor antixodant.
  157. (metadata workspace-metadata "metadata" (or-empty identity)))
  158. ;;;
  159. ;;; Individual crates (Rust terminology for packages) (no workspaces!)
  160. ;;;
  161. ;; The default crate type. (rlib = rust static library)
  162. ;;
  163. ;; For grafts and perhaps for space savings, we should probably switch to
  164. ;; 'dylib'. However, Rust projects are used to inter-crate LTO, so there
  165. ;; might be performance concerns. It might be possible to have rlib
  166. ;; for some crates and dylib for others, but this has not yet been
  167. ;; investigated.
  168. (define %default-crate-type "rlib")
  169. ;; By convention, tests named 'version-numbers' check that the version
  170. ;; in the Cargo.toml corresponds to version numbers in the README.md.
  171. ;; This is nice, but those tests use the crate version_sync, which has
  172. ;; quite a few (indirect) dependencies. Running these tests does not seem
  173. ;; worth the additional dependencies, skip such tests by default.
  174. (define %default-skipped-integration-tests
  175. '("version-number" "version-numbers"
  176. "version_number" "version_numbers"
  177. "version")) ; rust-hostname
  178. ;;;
  179. ;;; Reading Cargo.toml files.
  180. ;;;
  181. (define (or-constant constant)
  182. (lambda (proc)
  183. (lambda (foo)
  184. (if (unspecified? foo)
  185. constant
  186. (proc foo)))))
  187. (define or-false (or-constant #false))
  188. (define or-empty (or-constant '()))
  189. (define or-false* ((or-constant #false) identity))
  190. (define or-true* ((or-constant #true) identity))
  191. (define or-emptystring* ((or-constant "") identity))
  192. ;; rust-libc does not compile with edition=2018
  193. (define %default-edition "2015")
  194. (define or-default-edition* ((or-constant %default-edition) identity))
  195. (define (fixup-section-names scm)
  196. ;; Some packages, e.g. rust-smallvec, use dev_dependencies instead of dev-dependencies
  197. ;; or proc_macro instead of proc-macro.
  198. (define fixup-section-name
  199. (match-lambda
  200. ((name . value)
  201. (cons (string-replace-substring name "_" "-") value))))
  202. (map fixup-section-name scm))
  203. (define-json-mapping <package> make-package package?
  204. %json->package <=> %package->json <=> scm->package <=> package->scm
  205. (autobins package-autobins "autobins" or-true*) ; boolean
  206. (autoexamples package-autoexamples "autoexamples" or-true*) ; boolean
  207. (autotests package-autotests "autotests" or-true*) ; boolean
  208. (autobenches package-autobenches "autobenches" or-true*) ; boolean
  209. (version package-version "version" or-emptystring*) ; string
  210. (authors package-authors "authors" (or-empty vector->list)) ; vector of strings
  211. (categories package-categories "categories" (or-empty vector->list)) ; vector of strings
  212. (name package-name) ; string
  213. (description package-description "description" or-emptystring*) ; string
  214. (homepage package-homepage "homepage" or-emptystring*) ; string
  215. (repository package-repository "repository" or-emptystring*) ; string
  216. (license package-license "license" or-emptystring*) ; string
  217. (license-file package-license-file "license-file" or-emptystring*) ; string
  218. (edition package-edition "edition" or-default-edition*) ; string
  219. (build package-build "build" or-false*)
  220. (links package-links "links" or-false*)) ; string, despite the s suffix
  221. ;; TODO: not yet used. Maybe in the future we could check for
  222. ;; version incompatibilities?
  223. (define-json-mapping <dependency> make-dependency dependency?
  224. %json->dependency <=> %package->dependency <=> scm->dependency <=> package->dependency
  225. ;; 'name' is the name of the crate, inside the current Rust project.
  226. ;; By default, the name inside the crate is the name ooutside the crate.
  227. ;; However, a crate can choose to use a crate that names itself 'foo'
  228. ;; but use it as-if it was named 'bar', by setting 'name' to "bar"
  229. ;; and 'package' to "foo".
  230. ;;
  231. ;; 'name' is not actually part of the JSON / TOML.
  232. (name dependency-name) ; string
  233. (package dependency-package "package" or-false*) ; string | #false
  234. (optional %dependency-optional) ; boolean
  235. (path %dependency-path) ; string | #false
  236. (version %dependency-version) ; string | #false
  237. (git %dependency-git) ; string | #false
  238. (branch %dependency-branch) ; string | #false
  239. (default-features %dependency-default-features) ; boolean
  240. (registry %dependency-registry)) ; string | #false
  241. (define (scm->dependency-list scm)
  242. (define f
  243. (match-lambda
  244. ((key . value)
  245. (match value
  246. ((? string? version)
  247. (scm->dependency `(("name" . ,key) ("version" . ,version))))
  248. ((? list?) (scm->dependency `(("name" . ,key) ,@value)))))))
  249. (map f scm))
  250. ;;
  251. ;; <https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target>
  252. ;;
  253. ;; For a [lib], [[bin]], [[example]], [[test]] or [[bench]] section.
  254. ;;
  255. (define-json-mapping <target> make-target target?
  256. %json->target <=> %target->json <=> %scm->target <=> target->scm
  257. (name target-name "name" or-false*)
  258. (path target-path "path" or-false*)
  259. (test %target-test)
  260. (doctest %target-doctest)
  261. (bench %target-bench)
  262. (doc %target-doc)
  263. (plugin %target-plugin)
  264. (proc-macro target-proc-macro "proc-macro" or-false*)
  265. (harness %target-harness)
  266. (edition target-edition "edition" or-false*)
  267. (crate-type target-crate-type
  268. "crate-type"
  269. ((or-constant (list %default-crate-type))
  270. (lambda (x)
  271. (if (string? x)
  272. (list x)
  273. (vector->list x)))))
  274. ;; NA for [lib]
  275. (required-features target-required-features "required-features"
  276. (or-empty vector->list)))
  277. (define (elaborated-target? target)
  278. (and (target-name target)
  279. (target-path target)
  280. (target-edition target)))
  281. ;; Some Cargo.toml use proc_macro instead of proc-macro.
  282. (define scm->target (compose %scm->target fixup-section-names))
  283. (define (scm->target-list s)
  284. (map scm->target (vector->list s)))
  285. (define-json-mapping <target-specific> make-target-specific? target-specific?
  286. %json->target-specific <=> %manifest->target-specific <=> %scm->target-specific <=> target-specific->scm
  287. (target %target-specific-target) ; string, not actually part of the json
  288. (dependencies target-specific-dependencies "dependencies" (or-empty scm->dependency-list))
  289. ;; For tests, examples and benchmarks
  290. (dev-dependencies target-specific-dev-dependencies "dev-dependencies" (or-empty scm->dependency-list))
  291. ;; For build scripts
  292. (build-dependencies target-specific-build-dependencies "build-dependencies" (or-empty scm->dependency-list)))
  293. (define scm->target-specific (compose %scm->target-specific fixup-section-names))
  294. (define-json-mapping <manifest> make-manifest manifest?
  295. %json->manifest <=> %manifest->json <=> %scm->manifest <=> manifest->scm
  296. (workspace manifest-workspace "workspace" (or-false scm->workspace))
  297. (package manifest-package "package" (or-false scm->package)) ; optional for workspaces
  298. (lib manifest-lib "lib" (or-false scm->target))
  299. (bin manifest-bin "bin" (or-empty scm->target-list))
  300. (bench manifest-bench "bench" (or-empty scm->target-list))
  301. (example manifest-example "example" (or-empty scm->target-list))
  302. (test manifest-test "test" (or-empty scm->target-list))
  303. (features manifest-features "features" (or-empty identity))
  304. (dependencies manifest-dependencies "dependencies" (or-empty scm->dependency-list))
  305. ;; For tests, examples and benchmarks
  306. (dev-dependencies manifest-dev-dependencies "dev-dependencies" (or-empty scm->dependency-list))
  307. ;; For build scripts
  308. (build-dependencies manifest-build-dependencies "build-dependencies" (or-empty scm->dependency-list))
  309. (target manifest-target-specific "target"
  310. ;; list of <target-specific>
  311. (or-empty
  312. (lambda (s)
  313. (map (match-lambda
  314. ((key . value)
  315. (scm->target-specific
  316. `(("target" . ,key) ,@value))))
  317. s)))))
  318. (define scm->manifest (compose %scm->manifest fixup-section-names))
  319. (define (convert-toml->json from to)
  320. (invoke "python3" "-c"
  321. "import sys, toml, json
  322. here = sys.argv[1]; there = sys.argv[2];
  323. t = toml.load(here);
  324. with open(there, \"w\") as out_file:
  325. json.dump(t, out_file);"
  326. from to))
  327. (define (open-manifest toml json)
  328. (convert-toml->json toml json)
  329. (define parsed
  330. (call-with-input-file json
  331. (lambda (port)
  332. (json->scm port))
  333. #:encoding "UTF-8"))
  334. (scm->manifest parsed))
  335. ;;
  336. ;; State.
  337. ;;
  338. (let-syntax ((define-state-parameters
  339. (syntax-rules ()
  340. ((_ (call-with-reset-state capture-state)
  341. (name initial-value) ...)
  342. (begin
  343. (define name (make-parameter initial-value))
  344. ...
  345. (define* (call-with-reset-state
  346. thunk #:optional
  347. (state `((name . ,initial-value) ...)))
  348. "Call THUNK in a context where the state of antioxidant
  349. is reset to its initial value. If STATE is set, reset to that state instead."
  350. ;; TODO: reword in terms of dynamic extent?
  351. (parameterize ((name (assq-ref state 'name)) ...)
  352. (thunk)))
  353. (define (capture-state)
  354. "Return a structure holding the current state. It can be
  355. passed to CALL-WITH-RESET-STATE."
  356. `((name . ,(name)) ...)))))))
  357. (define-state-parameters
  358. (call-with-reset-state capture-state)
  359. ;; Set in the 'choose-features' phase. Can be extended in later
  360. ;; (package-specific) phases, until the 'make-feature-closure'.
  361. (*features* '())
  362. (*configuration* '()) ;; set by 'configure'
  363. ;; TODO: inputs/native-inputs distinction
  364. (*c-libraries* '())
  365. (*c-library-directories* '())
  366. ;; Initialised by the 'load-manifest' phase.
  367. (*manifest* #false)
  368. (*library-destination* #f)
  369. (*save* #false))) ;; TODO: less impure
  370. ;; This macro is (TODO: will) be used by the workspaces code to isolate
  371. ;; the different members from each other a little.
  372. (define-syntax-rule (with-reset-state code code* ...) ; TODO: will be used by the workspaces implementation.
  373. (call-with-reset-state (lambda () code code* ...)))
  374. ;; Packages to test when modifying these two procedures:
  375. ;; * rust-clang-sys
  376. ;; * rust-seccomp-sys
  377. ;; * rust-bindgen
  378. ;; * rust-tectonic-xetex-layout (to make sure the order is correct)
  379. ;; * maybe other -sys crates
  380. (define* (add-c-library! library)
  381. "Link the crate to be compiled against C-LIBRARY -- i.e., do the rust
  382. equivalent of adding \"-lLIBRARY ...\" to the invocation of \"gcc\"."
  383. (let ((corrected-library
  384. (cond ((string-suffix? ".so" library) ; happens for rust-jemalloc-sys@0.3
  385. (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)
  386. (string-drop-right library (string-length ".so")))
  387. ((string-suffix? ".a" library) ; not yet encountered in practice
  388. (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)
  389. (string-drop-right library (string-length ".a")))
  390. ;; TODO: .a case?
  391. (#true library))))
  392. ;; It is important to add the library at the end instead of the beginning,
  393. ;; to avoid "libstdc++: error adding symbols: DSO missing from command line'
  394. ;; -- order matters!
  395. (*c-libraries*
  396. (append (*c-libraries*) (list corrected-library)))))
  397. (define* (add-c-library-directory! library-directory)
  398. "Search for non-Rust libraries in LIBRARY-DIRECTORY -- i.e., do the rust
  399. equivalent of adding \"-LLIBRARY_DIRECTORY\" to the invocation of \"gcc\"."
  400. (*c-library-directories* (cons library-directory (*c-library-directories*))))
  401. ;;
  402. ;; Information on how to use a crate.
  403. ;;
  404. ;; <crate-information> loaded with 'load-crate-information' can be compared with eq?.
  405. ;; By default, it is assumed <crate-information> is loaded with that.
  406. (define-json-mapping <crate-information> make-crate-information crate-information?
  407. json->crate-information <=> crate-information->json <=>
  408. scm->crate-information <=> crate-information->scm
  409. ;; The following two fields are usually but not always the same:
  410. ;; for rust-debug-unreachable, the first in "debug_unreachable"
  411. ;; and the second is "new_debug_unreachable".
  412. (name crate-information-name) ; string, name of the crate (normalised)
  413. (dependency-name crate-information-dependency-name) ; string, name of the crate put as listed in the dependency information
  414. (link crate-information-link) ; string
  415. ;; Where is the crate (as .rlib or .so or such) located in the file system?
  416. ;; (TODO: check that it's absolute)
  417. (location crate-information-location) ; string
  418. ;; Extra libraries to add (as -l arguments) to compile depending crates.
  419. ;; static= prefixes are allowed.
  420. (libraries crate-information-libraries "libraries" vector->list list->vector)
  421. ;; List of directory names to search for the libraries -- without native=
  422. ;; prefixes or such!
  423. ;; TODO: check that they are absolute.
  424. (library-directories crate-information-library-directories "library-directories" vector->list list->vector)
  425. ;; List of file names of the (non-test, non-build, non-dev) dependencies of
  426. ;; this crate -- the file names point to a <crate-information> JSON.
  427. (dependencies crate-information-dependencies "dependencies" vector->list list->vector)
  428. (environment crate-information-environment)) ;; TODO
  429. ;;;
  430. ;;; Crate information that has been discovered or made so far.
  431. ;;; It acts as a memoisation table, to reduce the number of file system
  432. ;;; accesses.
  433. ;;;
  434. (define *known-crate-information* (make-hash-table)) ; file name -> <crate-information>
  435. (define *crate-information->file-name* (make-hash-table))
  436. (define (add-known-crate-information! parsed location)
  437. "Add PARSED, a <crate-information>, to the known crate information
  438. and associate it with LOCATION. If there is already an entry for LOCATION,
  439. it is replaced."
  440. (hash-set! *known-crate-information* location parsed)
  441. (hashq-set! *crate-information->file-name* parsed location))
  442. (define (load-crate-information location)
  443. "Load crate information at LOCATION and return it. As a side effect,
  444. add it to the known crate information. As an optimisation, if the location is
  445. already present in the known crate information, it can be reused."
  446. (match (hash-ref *known-crate-information* location)
  447. (#f (let ((parsed
  448. (scm->crate-information
  449. (call-with-input-file location
  450. json->scm
  451. #:encoding "UTF-8"))))
  452. (add-known-crate-information! parsed location)
  453. parsed))
  454. ((? crate-information? info) info)))
  455. (define (crate-information->file-name crate-info)
  456. (or (hashq-ref *crate-information->file-name* crate-info)
  457. (error (pk 'crate-info crate-info "unknown crate info"))))
  458. (define (save-crate-information! location crate-information)
  459. "Write CRATE-INFORMATION (a <crate-information>) to LOCATION. As a side
  460. effect, add it to the known crate information. If an entry already exists
  461. for LOCATION, it is overwritten. Parent directories of LOCATION are assumed
  462. to already exists, if not, an appropriate I/O exception is raised."
  463. (call-with-output-file location
  464. (lambda (o) (scm->json (crate-information->scm crate-information) o))
  465. #:encoding "UTF-8"))
  466. ;; Crate names are normalised by the constructor.
  467. (define-record-type (<crate-mapping> %make-crate-mapping crate-mapping?)
  468. ;; From which crate package does the crate come? This is usually, but
  469. ;; not always, the same as the name of the crate.
  470. ;; For 'rust-debug-unreachable', this is "new_debug_unreachable".
  471. (fields (immutable dependency-name crate-mapping-dependency-name) ; string
  472. ;; What does the crate that is using this crate
  473. ;; expect as name (for 'extern ...')? If #false,
  474. ;; default to the crate name (for rust-debug-unreachable,
  475. ;; that is "debug_unreachable").
  476. (immutable local-name %crate-mapping-local-name) ; string | #false
  477. ))
  478. (define crate-mapping-local-name
  479. (case-lambda
  480. ((crate-mapping)
  481. (or (%crate-mapping-local-name crate-mapping)
  482. (error "desired name of crate unknown, pass a <crate-information> to elaborate")))
  483. ((crate-mapping crate)
  484. (unless (crate-mapping? crate-mapping)
  485. (error "argument not a <crate-mapping>"))
  486. (unless (crate-information? crate)
  487. (error "argument not a <crate-information>"))
  488. (or (%crate-mapping-local-name crate-mapping)
  489. (crate-information-name crate)))))
  490. (define (make-crate-mapping dependency-name local-name)
  491. (%make-crate-mapping (normalise-crate-name dependency-name)
  492. (and=> local-name normalise-crate-name)))
  493. (define (normalise-crate-name name)
  494. (string-replace-substring name "-" "_"))
  495. (define (crate-name-of-manifest manifest)
  496. "Return the crate name of the crate specified in MANIFEST."
  497. ;; The 'rust-new-debug-unreachable' crate uses the name
  498. ;; 'debug_unreachable' and not 'new_debug_unreachable'.
  499. ;; So when available, use (target-name lib), otherwise
  500. ;; the build of rust-string-cache@0.8.0 fails.
  501. (let ((package (manifest-package (*manifest*)))
  502. (lib (manifest-lib (*manifest*))))
  503. (or (and=> lib target-name)
  504. (normalise-crate-name (package-name package)))))
  505. (define (partition-crates available-crates crate-mappings)
  506. ;; First return value: direct dependencies
  507. ;; Second return value: indirect dependencies (can contain things not in available-crates!)
  508. ;; Third return value: all things in available-crates not in the previous.
  509. ;;
  510. ;; Direct and indirect dependencies can overlap (e.g.: rust-syn@1.0.82)
  511. (define direct
  512. (filter (lambda (crate-information)
  513. (any (cut match? crate-information <>) crate-mappings))
  514. available-crates))
  515. (define (find-indirect from append-to)
  516. (define (f crate-information)
  517. (map load-crate-information
  518. (crate-information-dependencies crate-information)))
  519. (delete-duplicates (append (append-map f from) append-to) eq?))
  520. (let loop ((indirect (find-indirect direct '())))
  521. (let ((next (find-indirect indirect indirect)))
  522. (if (equal? indirect next) ; fixpoint reached
  523. (values direct indirect
  524. (lset-difference eq? available-crates
  525. (lset-union eq? direct indirect)))
  526. (loop next)))))
  527. (define (filter-used-crates available-crates crate-mappings)
  528. (let* ((direct indirect rest (partition-crates available-crates crate-mappings)))
  529. (append direct indirect)))
  530. (define (find-directly-available-crates inputs)
  531. (append-map (match-lambda
  532. ((_ . input)
  533. (let ((dir (string-append input "/lib/guixcrate")))
  534. (if (directory-exists? dir)
  535. (map load-crate-information
  536. (find-files dir "\\.crate-info"))
  537. '()))))
  538. inputs))
  539. (define (crate-directory store-item)
  540. (string-append store-item "/lib/guixcrate"))
  541. (define* (crate-library-destination crate-name type #:key outputs #:allow-other-keys)
  542. (string-append
  543. (crate-directory (or (assoc-ref outputs "lib")
  544. (assoc-ref outputs "out")))
  545. "/lib" crate-name "." type))
  546. (define* (c-library-destination crate-name type #:key outputs #:allow-other-keys)
  547. (string-append
  548. (or (assoc-ref outputs "lib")
  549. (assoc-ref outputs "out"))
  550. "/lib/lib" crate-name "." type)) ; type = ".a" / ".so"
  551. (define (extract-crate-name lib)
  552. (string-drop
  553. (string-drop-right (basename lib)
  554. (cond ((string-suffix? ".rlib" lib)
  555. (string-length ".rlib"))
  556. ((string-suffix? ".so" lib)
  557. (string-length ".so"))
  558. ((string-suffix? ".a" lib)
  559. (string-length ".a"))
  560. (#true
  561. (format #t "Unrecognised: ~a~%" lib))))
  562. (string-length "lib")))
  563. (define (match? crate-information crate-mapping)
  564. (string=? (crate-mapping-dependency-name crate-mapping)
  565. (crate-information-dependency-name crate-information)))
  566. (define (extern-arguments available-crates crate-mappings)
  567. (define (process-mapping crate-mapping)
  568. (define (do crate)
  569. (string-append "--extern=" (crate-mapping-local-name crate-mapping crate)
  570. "=" (crate-information-location crate)))
  571. ;; Search for a matchin crate
  572. (match (filter (cut match? <> crate-mapping) available-crates)
  573. (()
  574. (format (current-error-port)
  575. "warning: ~a not found in the available crates -- this might cause the build to fail!~%"
  576. crate-mapping)
  577. #f)
  578. ((x) (do x))
  579. ((x y . rest)
  580. (format (current-error-port)
  581. "warning: multiple candidates for ~a (~a, ~a) in the available crates -- this will probably cause the build to fail!~%"
  582. crate-mapping x y)
  583. (do x))))
  584. ;; "rustc" will sort out duplicates in crate-mappings (by emitting an error)(?)
  585. (filter-map process-mapping crate-mappings))
  586. (define* (L-arguments available-crates crate-mappings #:optional
  587. (extra-library-directories '()))
  588. (let* ((direct-dependencies indirect-dependencies rest
  589. (partition-crates available-crates crate-mappings))
  590. (indirect-crate->argument
  591. (lambda (crate-information)
  592. (string-append "-Ldependency="
  593. (dirname (crate-information-location crate-information)))))
  594. ;; No need for -Lcrate, as the full file name is passed to --extern=.
  595. (indirect-crate-arguments
  596. (map indirect-crate->argument indirect-dependencies))
  597. (make-Lnative-argument
  598. (lambda (directory)
  599. ;; native means something different in rustc than Guix.
  600. ;; In Rust, 'native' means non-Rust compiled libraries.
  601. (string-append "-Lnative=" directory)))
  602. (make-Lnative-arguments*
  603. (lambda (crate-information)
  604. (map make-Lnative-argument
  605. (crate-information-library-directories crate-information))))
  606. (Lnative-arguments
  607. (append (map make-Lnative-argument extra-library-directories)
  608. ;; Only use crates that are actually (indirectly) requested.
  609. (append-map make-Lnative-arguments*
  610. (append direct-dependencies indirect-dependencies)))))
  611. ;; Delete duplicates to shrink the invocation of 'rustc' a bit.
  612. (append (delete-duplicates Lnative-arguments string=?)
  613. indirect-crate-arguments))) ; shouldn't contain duplicates
  614. (define (configuration-arguments configuration)
  615. (append-map (lambda (cfg)
  616. (list "--cfg" cfg))
  617. configuration))
  618. (define* (l-arguments available-crates crate-mappings #:optional
  619. (extra-nonrust-libraries '()))
  620. ;; Only involve crates that are actually requested.
  621. ;; Result: a list of -lopenssl, -lstatic=ring-test, ..., arguments.
  622. (let* ((used-dependencies (filter-used-crates available-crates crate-mappings))
  623. (library->argument
  624. (lambda (library)
  625. (string-append "-l" library)))
  626. (crate->l-arguments
  627. (lambda (crate-information)
  628. (map library->argument
  629. (crate-information-libraries crate-information)))))
  630. (delete-duplicates ; shrink invocation of 'rustc'
  631. (append (map library->argument extra-nonrust-libraries)
  632. (append-map crate->l-arguments used-dependencies))
  633. string=?)))
  634. ;; TODO: untested, for newsboat
  635. (define* (L-arguments/non-rustc available-crates crate-mappings)
  636. "Return a list of -L arguments to be passed to a compiler like gcc to link
  637. to the crates in CRATE-MAPPINGS."
  638. ;; gcc doesn't make a -Lnative / -Ldependency / -Lcrate distinction
  639. (let* ((used-dependencies (filter-used-crates available-crates crate-mappings))
  640. (make-L-argument
  641. (lambda (directory)
  642. (string-append "-L" directory)))
  643. (compiled-crate-argument ; for linking to the compiled crate itself (.rlib|so|a|...)
  644. (lambda (crate-information)
  645. (make-L-argument
  646. (dirname (crate-information-location crate-information)))))
  647. (compiled-crate-arguments
  648. (map compiled-crate-argument used-dependencies))
  649. (nonrust-library-arguments*
  650. (lambda (crate-information)
  651. (map make-L-argument
  652. (crate-information-library-directories crate-information))))
  653. (nonrust-library-arguments
  654. ;; Only use crates that are actually (indirectly) requested.
  655. (append-map nonrust-library-arguments* used-dependencies)))
  656. ;; Delete duplicates to shrink the invocation of the C compiler a bit.
  657. (delete-duplicates (append compiled-crate-arguments nonrust-library-arguments))))
  658. ;; TODO: likewise untested!
  659. ;; TODO: for cdylib/dylib/staticlib crates, maybe this should include
  660. ;; the crate itself as well in -l?
  661. (define* (l-arguments/non-rustc available-crates crate-mappings)
  662. "Return a list of -l arguments to be passed to a compiler like gcc to link
  663. to the crates in CRATE-MAPPINGS."
  664. (define (derustify argument)
  665. (string-append "-l"
  666. (string-drop argument
  667. (cond ((string-prefix? "-lstatic=" argument)
  668. (string-length "-lstatic="))
  669. ((string-prefix? "-ldylib=" argument)
  670. (string-length "-ldylib="))
  671. ((string-prefix? "-lframework=" argument)
  672. (error "frameworks not supported"))
  673. ((string-prefix? "-l" argument)
  674. (string-length "-l"))
  675. (#true
  676. (pk 'unrecognised argument)
  677. (error "unrecognised library argument"))))))
  678. (delete-duplicates
  679. (map derustify (l-arguments available-crates crate-mappings))))
  680. (define (linker-arguments/non-rustc available-crates crate-mappings)
  681. (append (L-arguments/non-rustc available-crates crate-mappings)
  682. (l-arguments/non-rustc available-crates crate-mappings)))
  683. (define* (compile-rust source destination extra-arguments
  684. #:key inputs native-inputs outputs
  685. target
  686. (invoke (@ (guix build utils) invoke))
  687. (optimisation-level "1")
  688. (debuginfo-level "1")
  689. (rust-metadata 'automatic)
  690. (configuration '())
  691. (available-crates '())
  692. (crate-mappings '())
  693. (extra-libraries (*c-libraries*))
  694. (extra-library-directories (*c-library-directories*))
  695. #:allow-other-keys)
  696. (mkdir-p (dirname destination))
  697. (apply invoke
  698. "rustc" "--verbose"
  699. "-Z" "macro-backtrace" ; enable backtraces in macros during compilation, can help with debugging.
  700. (string-append "--target=" target)
  701. "-C" (string-append "opt-level=" optimisation-level)
  702. "-C" (string-append "debuginfo=" debuginfo-level)
  703. ;; Cargo adds '--extern=proc_macro' by default,
  704. ;; see <https://github.com/rust-lang/cargo/pull/7700>.
  705. ;; Make sure that it will be used.
  706. "--extern=proc_macro"
  707. "--cap-lints" "warn" ;; ignore #[deny(warnings)], it's too noisy
  708. "-C" "prefer-dynamic" ;; for C dependencies & grafting and such?
  709. ;; Two crates with the same name can only be used in the same binary
  710. ;; if they have different metadata, so give every crate unique
  711. ;; metadata. Destinations are (typically) locations in the store,
  712. ;; so it should usually be unique.
  713. "-C" (string-append "metadata="
  714. (if (eq? rust-metadata 'automatic)
  715. destination
  716. rust-metadata))
  717. source "-o" destination
  718. (append (extern-arguments available-crates crate-mappings)
  719. (L-arguments available-crates crate-mappings extra-library-directories)
  720. (configuration-arguments configuration)
  721. (l-arguments available-crates crate-mappings extra-libraries)
  722. extra-arguments)))
  723. (define* (compile-rust-library source destination crate-name extra-arguments
  724. #:key (crate-type %default-crate-type)
  725. (rust-dynamic-library-arguments #f)
  726. #:allow-other-keys
  727. #:rest arguments)
  728. (apply compile-rust source destination
  729. (append (list (string-append "--crate-name=" crate-name)
  730. (string-append "--crate-type=" crate-type))
  731. (if (string=? crate-type "cdylib")
  732. (or rust-dynamic-library-arguments
  733. (error "I don't know what symbols to export or the version of the library, please set #:rust-dynamic-library-arguments"))
  734. '())
  735. (if (string=? crate-type "dylib") ; TODO: untested!
  736. (or rust-dynamic-library-arguments '())
  737. '())
  738. extra-arguments)
  739. arguments))
  740. (define* (compile-rust-binary source destination extra-arguments
  741. #:key outputs #:allow-other-keys
  742. #:rest arguments)
  743. (apply compile-rust source destination
  744. (append (list "--crate-type=bin")
  745. extra-arguments)
  746. arguments))
  747. ;;;
  748. ;;; Features.
  749. ;;;
  750. (define (features-closure features features-section)
  751. "Include features and the features implied by those features and so on."
  752. (define new-features
  753. (delete-duplicates
  754. ;; lists are not sets, and the order is irrelevant here, so
  755. ;; pick some fixed arbitrary order.
  756. (sort-list
  757. (append-map (lambda (feature)
  758. (define extra
  759. (append
  760. (vector->list
  761. (or (assoc-ref features-section feature) #()))
  762. ;; "package-name/feature-name" is used for enabling
  763. ;; optional dependencies. Apparently, when enabling
  764. ;; optional dependencies, some crates expect the
  765. ;; "package-name" feature to be enabled as well?
  766. ;; (at least rust-pkcs1@0.3.3)
  767. (match (string-index feature #\/)
  768. ((? integer? k)
  769. (list (substring feature 0 k)))
  770. (#false '()))))
  771. (cons feature extra))
  772. features)
  773. string<?)))
  774. (if (equal? features new-features)
  775. ;; fixpoint has been reached
  776. features
  777. (features-closure new-features features-section)))
  778. (define (feature->config feature)
  779. ;; TODO: escapes?
  780. (string-append "feature=\"" feature "\""))
  781. (define* (choose-features #:key (features '("default")) #:allow-other-keys)
  782. "Initialise *features* according to #:features. By default, this enables
  783. the \"default\" feature, and the later 'make-feature-closure' will enable all
  784. default features implied by the \"default\" feature."
  785. (define maybe-car
  786. (match-lambda
  787. (("nightly" . _) #false) ; unlikely to work in Guix, e.g. rust-lock-api@0.4
  788. (("unstable" . _) #false) ; likewise, e.g. rust-fallible-collections@0.4.2
  789. (("vendored" . _) #false) ; not desired in Guix (e.g.: rust-libnghttp2-sys)
  790. (("vendor" . _) #false) ; plausible alternate spelling for same concept
  791. (("bundle" . _) #false) ; likewise
  792. (("bundled" . _) #false)
  793. ((x . y) x)))
  794. (match (list (->bool (member "default" features))
  795. (->bool (assoc "default" (manifest-features (*manifest*)))))
  796. ((#t #f)
  797. ;; See: https://doc.rust-lang.org/cargo/reference/features.html,
  798. ;; ‘the default feature’.
  799. (format #t "The default features are requested but the defaults are not
  800. chosen, enabling all features like Cargo does (with some exceptions).~%")
  801. (*features* (append (filter-map maybe-car (manifest-features (*manifest*)))
  802. features
  803. (*features*))))
  804. ((#f _)
  805. (format #t "warning: not enabling the default features!~%")
  806. (format #t "Using the features ~a and their implied features.~%" features)
  807. (*features* (append features (*features*))))
  808. (_
  809. (format #t "Using the features ~a and their implied features.~%" features)
  810. (*features* (append features (*features*)))))
  811. (*features* (delete-duplicates (*features*))))
  812. (define* (make-features-closure #:key (features '()) #:allow-other-keys)
  813. (define (forbid-vendoring feature)
  814. (when (member feature (*features*))
  815. (unless (member feature features)
  816. (format (current-error-port)
  817. "The vendoring feature ~a was implicitly enabled, but vendoring is usually considered unacceptable due to reasons, so the build is halted. To vendor anyway, explicitly enable the feature.~%"
  818. feature)
  819. (exit 1))))
  820. (*features* (features-closure (*features*) (manifest-features (*manifest*))))
  821. (forbid-vendoring "vendored")
  822. (forbid-vendoring "vendor")
  823. (forbid-vendoring "bundle")
  824. (forbid-vendoring "bundled")
  825. (format #t "The following features will be used: ~a~%." (*features*)))
  826. ;; Fake cargo crates that antioxidant doesn't need
  827. (define %rustc-std-workspace-crates
  828. (map normalise-crate-name
  829. '("rustc-std-workspace-std"
  830. "rustc-std-workspace-core"
  831. "rustc-std-workspace-alloc")))
  832. ;; If too many crates are included in --extern, errors like
  833. ;; error[E0659]: `time` is ambiguous (name vs any other name during import resolution)
  834. ;; are possible. Avoid them!
  835. (define* (manifest-all-dependencies manifest #:optional (kinds '(dependency dev build)))
  836. "Return a list of crates that are dependencies, as <crate> records."
  837. ;; For now ignore which target a dependency is for.
  838. (define (the-target-specific-dependencies target-specific)
  839. (append (if (memq 'dependency kinds)
  840. (target-specific-dependencies target-specific)
  841. '())
  842. (if (memq 'dev kinds)
  843. (target-specific-dev-dependencies target-specific)
  844. '())
  845. (if (memq 'build kinds)
  846. (target-specific-build-dependencies target-specific)
  847. '())))
  848. (define dependencies
  849. (append (if (memq 'dependency kinds)
  850. (manifest-dependencies manifest)
  851. '())
  852. (if (memq 'dev kinds)
  853. (manifest-dev-dependencies manifest)
  854. '())
  855. (if (memq 'build kinds)
  856. (manifest-build-dependencies manifest)
  857. '())
  858. (append-map the-target-specific-dependencies
  859. (manifest-target-specific manifest))))
  860. (define (construct-crate dependency)
  861. (make-crate-mapping (or (dependency-package dependency)
  862. (dependency-name dependency))
  863. (and (dependency-package dependency) ; <-- first clause required for rust-new-debug-unreachable / rust-string-cache@0.8.0
  864. (dependency-name dependency))))
  865. (define (fake? mapping) ;; avoid warnings about fake crates being missing
  866. (member (crate-mapping-dependency-name mapping) %rustc-std-workspace-crates))
  867. (filter (negate fake?) (map construct-crate dependencies)))
  868. ;; Some cargo:??? lines from build.rs are ‘propagated’ to dependencies
  869. ;; as environment variables, see
  870. ;; <https://doc.rust-lang.org/cargo/reference/build-script-examples.html>.
  871. (define* (read-dependency-environment-variables
  872. #:key (inputs '())
  873. (native-inputs '())
  874. (outputs '())
  875. #:allow-other-keys)
  876. ;; TODO: also for indirect dependencies?
  877. (define (setenv* x y)
  878. (format #t "setting ~a to ~a~%" x y)
  879. (setenv x y))
  880. (define (drop-native=-prefix directory)
  881. ;; Strip native= and all= prefixes from 'directory'
  882. (cond ((string-prefix? "native=" directory)
  883. (string-drop directory (string-length "native=")))
  884. ((string-prefix? "all=" directory)
  885. (string-drop directory (string-length "all=")))
  886. (#t directory)))
  887. (define (do crate-info)
  888. (unless (null? (crate-information-environment crate-info))
  889. ;; Don't spam the build log with do-nothing messages
  890. ;; if there are no actual environment variables to set.
  891. (format #t "setting extra environment variables in ~a~%"
  892. (crate-information->file-name crate-info)))
  893. (for-each
  894. (match-lambda
  895. ((x . y) (setenv*
  896. (string-replace-substring
  897. (string-upcase
  898. (string-append
  899. "DEP_"
  900. (crate-information-link crate-info)
  901. "_"
  902. x))
  903. "-"
  904. "_")
  905. y)))
  906. (crate-information-environment crate-info)))
  907. ;; 'outputs': in case of workspace crates
  908. (for-each do
  909. (find-directly-available-crates
  910. (delete-duplicates (append native-inputs inputs outputs)))))
  911. (define* (save-crate-info link-name saved-settings library-destination
  912. #:key inputs outputs #:allow-other-keys)
  913. (define where (string-append (or (assoc-ref outputs "env")
  914. (assoc-ref outputs "lib")
  915. (assoc-ref outputs "out")) ;; maybe switch the last two?
  916. "/lib/guixcrate/" link-name ".crate-info"))
  917. (define available-crates
  918. ;; 'outputs': in case of workspace crates
  919. (find-directly-available-crates (append inputs outputs)))
  920. (define crate-mappings (manifest-all-dependencies (*manifest*) '(dependency)))
  921. (format #t "Saving crate information in ~a~%" where)
  922. (mkdir-p (dirname where))
  923. ;; /tmp/guix-build-... directories won't exist after the build is finished,
  924. ;; so including them is pointless.
  925. (define (directory-without-prefix dir)
  926. (cond ((string-prefix? "native=" dir)
  927. (string-drop dir (string-length "native=")))
  928. ((string-prefix? "all=" dir)
  929. (string-drop dir (string-length "all=")))
  930. (#t dir)))
  931. (define (local-directory? dir)
  932. (string-prefix? (getcwd) (directory-without-prefix dir)))
  933. ;; If the build.rs compiled a C library and linked it into the crate,
  934. ;; then at least for cases known at writing, rustc will link the local
  935. ;; C library into the rlib (rust-sha2-asm@0.6.1), so including them in
  936. ;; -l later is pointless, especially given that they won't be found later.
  937. (define (locally-compiled-c-library? foo)
  938. (let* ((name (if (string-prefix? "static=" foo)
  939. (string-drop foo (string-length "static="))
  940. foo))
  941. (basename (format #f "lib~a.a" name)))
  942. (define (match? c-library-directory)
  943. (and (local-directory? c-library-directory)
  944. (file-exists? (in-vicinity
  945. (directory-without-prefix c-library-directory)
  946. basename))))
  947. ;; rust-sha2-asm doesn't add the current directory to c-library-directories
  948. ;; even though it adds a static library there.
  949. (any match? (cons (getcwd) (*c-library-directories*)))))
  950. (define filtered-c-libraries
  951. (filter (negate locally-compiled-c-library?) (*c-libraries*)))
  952. (define filtered-library-directories
  953. (filter (negate local-directory?) (*c-library-directories*)))
  954. (save-crate-information!
  955. where
  956. (make-crate-information
  957. (crate-name-of-manifest (*manifest*))
  958. ;; TODO: should the dependency name be normalised?
  959. (normalise-crate-name (package-name (manifest-package (*manifest*))))
  960. link-name
  961. (*library-destination*)
  962. filtered-c-libraries
  963. filtered-library-directories
  964. ;; direct dependencies
  965. (map crate-information->file-name
  966. (partition-crates available-crates crate-mappings))
  967. ;; TODO: maybe filter out uninteresting things like
  968. ;; core-rerun-if-changed?
  969. saved-settings)))
  970. ;; To avoid cluttering the .crate-info and to reduce the number of environment
  971. ;; variables set, exclude these variables which aren't used by dependents.
  972. ;; Not exhaustive.
  973. (define %excluded-keys
  974. ;; 'include' is used by rust-tectonic-engine-bibtex@0.1.1
  975. '("rerun-if-env-changed" "rerun-if-changed" "rustc-link-search" "rustc-link-lib"
  976. "rustc-cfg" "warning"))
  977. (define* (configure #:key inputs native-inputs outputs
  978. target build optimisation-level
  979. #:allow-other-keys #:rest arguments)
  980. (define saved-settings '())
  981. (define (save! key value)
  982. "Add a KEY=VALUE mapping to the saved settings, unless it is excluded
  983. by %excluded-keys."
  984. (unless (member key %excluded-keys)
  985. (set! saved-settings (cons (cons key value) saved-settings))))
  986. (define extra-configuration '()) ; --cfg options, computed by build.rs
  987. (define (handle-line line)
  988. (when (string-prefix? "cargo:" line)
  989. (let* ((rest (string-drop line (string-length "cargo:")))
  990. (=-index (string-index rest #\=)))
  991. (if =-index
  992. (let ((this (substring rest 0 =-index))
  993. (that (substring rest (+ 1 =-index))))
  994. (save! this that))
  995. (begin
  996. (pk 'l rest)
  997. (error "cargo: line doesn't look right, = missing?")))))
  998. (cond ((string-prefix? "cargo:rustc-cfg=" line)
  999. (format #t "Building with --cfg ~a~%" line) ;; todo invalid
  1000. (set! extra-configuration
  1001. (cons (string-drop line (string-length "cargo:rustc-cfg="))
  1002. extra-configuration)))
  1003. ;; The rustc-link-lib and rustc-link-search will be added to the <crate-information>.
  1004. ((string-prefix? "cargo:rustc-link-lib=" line)
  1005. (let ((c-library (string-drop line (string-length "cargo:rustc-link-lib="))))
  1006. (format #t "Building with C library ~a~%" c-library)
  1007. (add-c-library! c-library)))
  1008. ((string-prefix? "cargo:rustc-link-search=" line)
  1009. (let ((KIND=PATH (string-drop line (string-length "cargo:rustc-link-search="))))
  1010. (cond ((string-prefix? "framework=" KIND=PATH)
  1011. (error "framework not yet supported"))
  1012. ((string-prefix? "native=" KIND=PATH)
  1013. (add-c-library-directory! (string-drop KIND=PATH (string-length "native="))))
  1014. ((string-prefix? "all=" KIND=PATH)
  1015. ;; Note (Cargo incompatibility?): technically the build.rs could ask us
  1016. ;; here to search for crates in some arbitrary directories (instead of
  1017. ;; only C-style libraries), but no crate(™) does that (so far ...)
  1018. (add-c-library-directory! (string-drop KIND=PATH (string-length "=all"))))
  1019. ((or (string-prefix? "crate=" KIND=PATH)
  1020. (string-prefix? "dependency=" KIND=PATH))
  1021. (error "The build script is not supposed to ask to look into arbitrary locations for crates."))
  1022. (#true
  1023. (add-c-library-directory! KIND=PATH)))))
  1024. ((string-prefix? "cargo:rustc-env=" line)
  1025. (putenv (string-drop line (string-length "cargo:rustc-env="))))
  1026. ((string-prefix? "cargo:warning=" line)
  1027. (format (current-error-port)
  1028. "configuration script: warning: ~a~%"
  1029. (string-drop line (string-length "cargo:warning="))))
  1030. ((or (string-prefix? "cargo:rerun-if-changed=" line)
  1031. (string-prefix? "cargo:rerun-if-env-changed=" line))
  1032. (values)) ; nothing to do for antioxidant, no need for a warning
  1033. ((string-prefix? "cargo:" line)
  1034. (pk 'l line)
  1035. (format #t "warning: ~a: unrecognised build.rs instruction~%" line)
  1036. (format #t "hint: maybe the crate is just saving an environment variable for dependencies, maybe nothing needs to be changed.\n"))
  1037. ;; Some build.rs (e.g. the one of rust-pico-sys)
  1038. ;; print strings like "TARGET = Some(\"TARGET\")". Maybe
  1039. ;; they are just debugging information that can be ignored
  1040. ;; by cargo -- err, antioxidant.
  1041. (#true
  1042. (format #t "info from build.rs: ~a~%" line))))
  1043. (setenv "CARGO_MANIFEST_DIR" (getcwd)) ; directory containing the Cargo.toml
  1044. (define package (manifest-package (*manifest*)))
  1045. (define build.rs
  1046. (or (package-build package)
  1047. ;; E.g, rust-proc-macros2 doesn't set 'build'
  1048. ;; even though it has a configure script.
  1049. (and (file-exists? "build.rs") "build.rs")))
  1050. (define (set-feature-environment-variable! feature)
  1051. ;; Some crates, e.g. rust-indexmap and rust-wayland-protocols
  1052. ;; expect CARGO_FEATURE_... environment variables to be set. See:
  1053. ;; <https://doc.rust-lang.org/cargo/reference/features.html#build-scripts>.
  1054. (setenv (string-append "CARGO_FEATURE_"
  1055. (string-replace-substring
  1056. (string-upcase feature) "-" "_"))
  1057. "1"))
  1058. (when build.rs
  1059. (format #t "building configuration script~%")
  1060. (apply
  1061. compile-rust-binary build.rs "configuration-script"
  1062. (list (string-append "--edition=" (package-edition package)))
  1063. (append arguments
  1064. ;; In Cargo, the build script _does not_ have access to dependencies
  1065. ;; in 'dependencies' or 'dev-dependencies', only 'build-dependencies',
  1066. ;; see
  1067. ;; <https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html>.
  1068. (list #:crate-mappings (manifest-all-dependencies (*manifest*) '(build))
  1069. #:available-crates
  1070. ;; 'outputs': when building workspace crates
  1071. (find-directly-available-crates (append outputs native-inputs))
  1072. ;; Build for the machine the configuration script will be run
  1073. ;; on.
  1074. #:target build ; todo: correct terminology?
  1075. #:configuration (map feature->config (*features*)))))
  1076. ;; Expected by rust-const-fn's build.rs
  1077. (setenv "OUT_DIR" (getcwd))
  1078. ;; Expected by rust-libm's build.rs
  1079. (setenv "OPT_LEVEL" optimisation-level)
  1080. ;; Expected by some configuration scripts, e.g. rust-libc
  1081. (setenv "RUSTC" (which "rustc"))
  1082. (for-each set-feature-environment-variable! (*features*))
  1083. (setenv "TARGET" target) ; used by rust-proc-macro2's build.rs
  1084. (setenv "HOST" build) ; used by rust-pico-sys
  1085. ;; TODO: use pipes
  1086. (format #t "running configuration script~%")
  1087. (with-output-to-file ".guix-config"
  1088. (lambda ()
  1089. (invoke "./configuration-script")))
  1090. (call-with-input-file ".guix-config"
  1091. (lambda (port)
  1092. (let loop ((r (get-line port)))
  1093. (match r
  1094. ((? string? line) (handle-line line) (loop (get-line port)))
  1095. ((? eof-object? line) (values)))))))
  1096. (*configuration* (append extra-configuration (map feature->config (*features*))))
  1097. (*save*
  1098. (lambda (library-destination)
  1099. (apply save-crate-info (or (package-links package)
  1100. (package-name package))
  1101. saved-settings library-destination
  1102. arguments)))
  1103. (format #t "Building with configuration options: ~a~%" (*configuration*)))
  1104. (define* (determine-crate-type manifest #:key rust-crate-type #:allow-other-keys #:rest arguments)
  1105. "Return the crate type to build this rust crate as."
  1106. (define lib (manifest-lib manifest))
  1107. (cond (rust-crate-type rust-crate-type) ; override
  1108. ((not lib) %default-crate-type)
  1109. ((target-proc-macro lib) "proc-macro")
  1110. (#true
  1111. (match (target-crate-type lib)
  1112. (() (error "There must be at least one crate type."))
  1113. ((x) x)
  1114. ((? list? rest)
  1115. (pk 'types rest 'in manifest)
  1116. (error "antioxidant only supports a single crate type, override Cargo.toml with #:rust-crate-type"))))))
  1117. (define* (build #:key rust-crate-type inputs outputs tests?
  1118. #:allow-other-keys #:rest arguments)
  1119. "Build the Rust crates (library) described in Cargo.toml. If tests are enabled,
  1120. also compile the tests using the mechanism described in
  1121. <https://doc.rust-lang.org/rustc/tests/index.html> and put the test binary in the
  1122. \"tests\" output (or \"bin\" or \"out\")."
  1123. ;; TODO: maybe allow _not_ putting them in an output?
  1124. ;; Also, putting them in "bin" or "out" is potentially confusing.
  1125. ;; Tested for: rust-cfg-il, rust-libc (TODO: more)
  1126. (let* ((package (manifest-package (*manifest*)))
  1127. (crate-mappings (manifest-all-dependencies (*manifest*) '(dependency)))
  1128. (lib (manifest-lib (*manifest*)))
  1129. (crate-name (crate-name-of-manifest (*manifest*)))
  1130. (edition (package-edition package))
  1131. ;; Location of the crate source code to compile.
  1132. ;; The default location is src/lib.rs, some packages put
  1133. ;; the code elsewhere.
  1134. (lib-path (or (and=> lib target-path)
  1135. (and (file-exists? "src/lib.rs") "src/lib.rs")))
  1136. (crate-type (apply determine-crate-type (*manifest*) arguments)))
  1137. (unless (member crate-type '("bin" "lib" "rlib" "dylib" "cdylib" "staticlib" "proc-macro"))
  1138. ;; Note: not all of these crate types have been tested.
  1139. (pk 'c crate-type)
  1140. (error "unrecognised crate type"))
  1141. (when (and (string=? crate-type "staticlib")
  1142. (not rust-crate-type))
  1143. (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"))
  1144. ;; TODO: implement proper library/binary autodiscovery as described in
  1145. ;; <https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery>.
  1146. (when lib-path
  1147. (*library-destination*
  1148. (apply (if (member crate-type '("cdylib")) ; TODO: maybe also for 'dylib'?
  1149. c-library-destination
  1150. crate-library-destination)
  1151. crate-name
  1152. (cond ((member crate-type '("cdylib" "dylib" "proc-macro"))
  1153. "so")
  1154. ((member crate-type '("staticlib")) ; used by newsboat-ffi
  1155. "a")
  1156. ((member crate-type '("rlib" "lib"))
  1157. "rlib")
  1158. (#true
  1159. (pk 'c crate-type)
  1160. (error "bogus crate type -- should be unreachable")))
  1161. arguments)) ;; TODO: less impure
  1162. (apply compile-rust-library lib-path (*library-destination*)
  1163. crate-name
  1164. ;; Version of the Rust language (cf. -std=c11)
  1165. ;; -- required by rust-proc-macro2
  1166. (list (string-append "--edition=" (package-edition package))
  1167. ;; Some build.rs put libraries in the current directory
  1168. ;; (or, at least, in OUT_DIR or something like that).
  1169. ;; TODO: can be done tidier.
  1170. ;; TODO: is this still necessary, now we interpret
  1171. ;; rustc-link-search and such?
  1172. (string-append "-Lnative=" (getcwd)))
  1173. #:crate-type crate-type
  1174. ;; 'outputs': when building workspace crates.
  1175. #:available-crates
  1176. (find-directly-available-crates (append outputs inputs))
  1177. #:crate-mappings crate-mappings
  1178. ;; TODO: does the order matter?
  1179. (append arguments (list #:configuration (*configuration*))))
  1180. ;; It is important to write the .crate-info only after actually
  1181. ;; compiling the library. Otherwise, if the library being compiled
  1182. ;; has the same name as one of its (direct) dependencies, then
  1183. ;; we would be telling 'rustc' to link to the not-yet-existing
  1184. ;; library itself instead of its dependency. For an example,
  1185. ;; see python-blake3@0.3.1.
  1186. ((*save*) (*library-destination*))
  1187. (when tests?
  1188. ;; Compile the tests
  1189. (apply compile-binary-target
  1190. (elaborate-target
  1191. (*manifest*)
  1192. (scm->target
  1193. `(("name" . ,(string-append crate-name "-embedded-tests"))
  1194. ("path" . ,lib-path))))
  1195. crate-name
  1196. #:family 'test
  1197. arguments)))))
  1198. ;; See <https://doc.rust-lang.org/cargo/guide/project-layout.html>
  1199. ;; for how source locations are inferred.
  1200. (define* (infer-binary-source target #:optional (type 'bin))
  1201. "Guess the Rust source code location of TARGET, a <target> record. If not found,
  1202. return false instead."
  1203. (define inferred-source0
  1204. (and (target-name target)
  1205. (case type
  1206. ((bin) (format #f "src/bin/~a.rs" (target-name target)))
  1207. ((test) (format #f "tests/~a.rs" (target-name target)))
  1208. (else (pk 't type) (error "unknown type")))
  1209. ;; TODO: for 100% paranoia, check that inferred-source0
  1210. ;; doesn't contain #\nul, slashes or .. components.
  1211. ))
  1212. ;; default executable (TODO: is this code path actually ever used?) (probably not)
  1213. (define inferred-source1 (and (eq? type 'bin) "src/main.rs"))
  1214. (or (target-path target) ; explicit
  1215. (and inferred-source0 (file-exists? inferred-source0) inferred-source0)
  1216. (and inferred-source1 (file-exists? inferred-source1) inferred-source1)))
  1217. (define* (compile-binary-target target/elaborated crate-name
  1218. #:key (destination 'auto)
  1219. (family 'bin)
  1220. (integration-test? #false)
  1221. inputs
  1222. outputs
  1223. #:allow-other-keys
  1224. #:rest arguments)
  1225. "Compile an elaborated target @var{target/elaborated}.
  1226. If 'destination' is a file name, the binary will be saved there.
  1227. If it is the symbol 'auto', an appropriate file name will be chosen
  1228. according to the 'target-name' or @var{target/elaborated} and @var{family}.
  1229. In that case, the binary will have the target-name as 'base name' and will
  1230. be put in the 'bin' subdirectory of one of the outputs.
  1231. If the file already exists, bail out.
  1232. The directory where the binary is saved in will automatically be created if
  1233. required.
  1234. The output is based on the symbol 'family' -- if this output does not exist in the list
  1235. of outputs, this procedure fallbacks to \"bin\" and then \"bin\" (except for 'test', where
  1236. it fallbacks to the directory '.guix-tests'.
  1237. The location of the binary is returned (as a string).
  1238. @begin itemize
  1239. @item bin: a regular binary, for the \"bin\" output
  1240. @item example: an example (corresponding to an [[example]] section in the
  1241. Cargo.toml terminology or a file in the 'examples' subdirectory), for
  1242. the \"examples\" output.
  1243. @item benchmark: a benchmark (corresponding to a [[bench]] section or a file in the
  1244. 'benches' directory)
  1245. @item test: a test (corresponding to a [[test]] section or a file in the 'tests' directory or the tests
  1246. embedded in the main source code)
  1247. @end itemize"
  1248. (unless (elaborated-target? target/elaborated)
  1249. (pk target/elaborated)
  1250. (error "The first argument to 'compile-binary-target' must be an elaborated target"))
  1251. (define %family->output
  1252. '((bin . "bin")
  1253. (example . "examples")
  1254. (benchmark . "benchmarks")
  1255. (test . "tests")))
  1256. (define binary-location
  1257. (match destination
  1258. ((? string? where)
  1259. (if (absolute-file-name? where)
  1260. where
  1261. (error "The file name passed to 'compile-binary-target' must be absolute.")))
  1262. ('auto
  1263. (match (assoc family %family->output)
  1264. (('test . output)
  1265. (let ((output-directory (assoc-ref outputs output)))
  1266. (string-append
  1267. (if output-directory
  1268. (string-append output-directory "/bin/")
  1269. ".guix-tests/")
  1270. (target-name target/elaborated))))
  1271. ((_ . output)
  1272. (string-append (or (assoc-ref outputs output)
  1273. (assoc-ref outputs "bin")
  1274. (assoc-ref outputs "out")
  1275. (error "'compile-binary-target' expects the \"out\" output to exist."))
  1276. "/bin/"
  1277. (target-name target/elaborated)))
  1278. (#false
  1279. (if (symbol? family)
  1280. (error "the family passed to 'compile-bin-target' is unrecognised")
  1281. (error "the family passed to 'compile-bin-target' is expected to be a symbol")))))))
  1282. (when (file-exists? binary-location)
  1283. ;; This identified a miscompilation of rust-os-pipe.
  1284. (error (format #f "~a already exists when building ~a, refusing to build to avoid overwrite~%"
  1285. binary-location target/elaborated)))
  1286. (format #t "Compiling ~a to ~a~%" (target-path target/elaborated) binary-location)
  1287. (apply compile-rust-binary
  1288. (target-path target/elaborated)
  1289. binary-location
  1290. (append
  1291. (if (eq? family 'test)
  1292. ;; TODO: does this work for [[tests]] and integration tests?
  1293. '("--test") ; let the tests be run instead of the main function
  1294. '())
  1295. (if (and (eq? family 'test) (not integration-test?))
  1296. ;; While tempting, '-C debug-assertions=on' may not be
  1297. ;; added unconditionally for _all_ tests, as some packages (*)
  1298. ;; have 'panic' tests that expect that the library was compiled
  1299. ;; with the same debug-assertions setting as the tests (tests can
  1300. ;; still do regular assert!-ions).
  1301. ;;
  1302. ;; * e.g. rust-easy-cast, rust-backtrace, rust-ndarray,
  1303. ;; rust-reqwest
  1304. ;;
  1305. ;; However, for the embedded tests, enabling them should be fine,
  1306. ;; as the library is recompiled for the embedded tests.
  1307. '("-C" "debug-assertions=on")
  1308. '())
  1309. (if crate-name
  1310. (list (string-append "--crate-name=" crate-name))
  1311. '())
  1312. (list (string-append "--edition=" (target-edition target/elaborated))
  1313. (string-append "-Lnative=" (getcwd)))) ; TODO: is this still required, now there's better support for configure scripts?
  1314. ;; A program can use its own crate without declaring it.
  1315. ;; At least, hexyl tries to do so. For a more complicated
  1316. ;; example, see 'rust-xml-rs@0.8.3', which has "xml_rs" as
  1317. ;; package name and "xml" as --extern name.
  1318. ;;
  1319. ;; TODO: there were ‘could not find crate FOO’ warnings, does this
  1320. ;; still have any effect?
  1321. #:crate-mappings
  1322. (append (if (and (eq? family 'test)
  1323. (not integration-test?))
  1324. ;; When compiling non-integration tests, we are at the
  1325. ;; same time compiling the library. Linking to a library
  1326. ;; when a variant of it is being compiled can cause import
  1327. ;; ambiguities (e.g. in case of rust-glib@0.14.8), so
  1328. ;; don't do that.
  1329. ;;
  1330. ;; For integration tests (e.g. rust-cfg-if@1.0.0), adding
  1331. ;; the library is required.
  1332. '()
  1333. (list (make-crate-mapping (package-name (manifest-package (*manifest*)))
  1334. (crate-name-of-manifest (*manifest*)))))
  1335. (manifest-all-dependencies (pk 'm (*manifest*))
  1336. (if (eq? family 'test)
  1337. '(dependency dev)
  1338. '(dependency))))
  1339. ;; Binaries can use their own crates!
  1340. ;; TODO: for tests, also native-inputs?
  1341. #:available-crates
  1342. (find-directly-available-crates (append outputs inputs))
  1343. ;; TODO: figure out how to override things
  1344. (append
  1345. arguments
  1346. (list #:configuration (*configuration*))))
  1347. binary-location)
  1348. (define-condition-type &missing-target-source-code &error
  1349. missing-target-source-code?
  1350. (target missing-target-source-code-target))
  1351. (define* (elaborate-target manifest target #:optional (type 'bin))
  1352. (define package (manifest-package manifest))
  1353. (set-fields target
  1354. ((target-name)
  1355. (or (target-name target) (package-name package)))
  1356. ((target-path)
  1357. (or (target-path target)
  1358. (infer-binary-source target type)
  1359. (raise
  1360. (condition (&missing-target-source-code
  1361. (target target))))))
  1362. ((target-edition)
  1363. (or (target-edition target)
  1364. (package-edition package)))))
  1365. (define* (elaborate-target/skip manifest target #:optional (type 'bin))
  1366. ;; Return the <target> on success, #false otherwise.
  1367. ;; #false: source code is missing.
  1368. ;;
  1369. ;; Maybe the file has been removed due to being non-free,
  1370. ;; requiring dependencies not packaged in Guix, or requiring
  1371. ;; a non-stable rust. This skipping used to be required for
  1372. ;; rust-phf-generator back when required-features wasn't expected
  1373. ;; and hence gen_hash_test.rs had to be removed in a phase.
  1374. (guard (c
  1375. ((missing-target-source-code? c)
  1376. (format #t "warning: source code of ~a could not be found, skipping.~%"
  1377. (missing-target-source-code-target c))
  1378. #false))
  1379. (elaborate-target manifest target type)))
  1380. (define (not-dot? entry)
  1381. (not (member (car entry) '("." ".."))))
  1382. (define (scan-for-targets bin-directory)
  1383. (filter-map
  1384. (match-lambda
  1385. ((file-name . _)
  1386. (let ((entry-file-name (string-append bin-directory "/" file-name)))
  1387. ;; Is it a file or a directory?
  1388. (match (stat:type (lstat entry-file-name))
  1389. ('regular
  1390. ;; If it is a rust file, use it! The binary will have the same name
  1391. ;; as the source file name, except for extension.
  1392. (and (string-suffix? ".rs" file-name)
  1393. (scm->target `(("name" . ,(string-drop-right file-name 3))
  1394. ("path" . ,entry-file-name)))))
  1395. ('directory
  1396. ;; If it contains a 'main.rs' file, use it!
  1397. (let ((main (string-append entry-file-name "/main.rs")))
  1398. (and (file-exists? main)
  1399. (eq? 'regular (stat:type (stat main)) )
  1400. (scm->target `(("path" . ,main)
  1401. ("name" . ,file-name)))))) ; Cargo documentation says: ‘The name of the executable will be the directory name’
  1402. (_ #false))))) ; something else (e.g., pipe), not something we can build.
  1403. ;; not-dot?: avoid looking for src/bin/../main.rs or compiling a '.' binary
  1404. ;; from src/bin/./main.rs, which caused a build failure for skim@0.9.4.
  1405. (scandir* bin-directory not-dot?)))
  1406. (define* (find-rust-binaries . arguments) ; TODO: extend to [[benches]], [[tests]], [[examples]]
  1407. ;; This implements autobins, as desribed in
  1408. ;; <https://doc.rust-lang.org/cargo/guide/project-layout.html>.
  1409. ;; As a side-effect, targets are automatically elaborated.
  1410. ;; If the source code of a [[bin]] section is missing, it is ignored
  1411. ;; (with a warning).
  1412. ;;
  1413. ;; First look in [[bin]] sections
  1414. ;;;
  1415. ;; Packages to test after modifications:
  1416. ;; * rust-os-pipe
  1417. ;; * ???
  1418. (let* ((autobins? (package-autobins (manifest-package (*manifest*))))
  1419. (elaborate-target/skip* (cut elaborate-target/skip (*manifest*) <>))
  1420. (explicit-binaries
  1421. (filter-map elaborate-target/skip* (manifest-bin (*manifest*))))
  1422. (implicit-primary-main-binary
  1423. (and autobins?
  1424. (file-exists? "src/main.rs")
  1425. (elaborate-target/skip* (scm->target `(("path" . "src/main.rs"))))))
  1426. (implicit-other-main-binaries
  1427. (and autobins?
  1428. (directory-exists? "src/bin")
  1429. (scan-for-targets "src/bin")))
  1430. (implicit-targets
  1431. (filter-map
  1432. elaborate-target/skip*
  1433. (append (or (and=> implicit-primary-main-binary list)
  1434. '())
  1435. (or implicit-other-main-binaries '()))))
  1436. ;; If it's already compiled in the explicit-binaries, don't double compile.
  1437. ;; (We needed to elaborate-target, because we use the file name
  1438. ;; which is not always listed.). Likewise for the target name.
  1439. (already-used?
  1440. (lambda (target)
  1441. (or (member (target-path target) (map target-path explicit-binaries))
  1442. (member (target-name target) (map target-name explicit-binaries)))))
  1443. (filtered-implicit-targets
  1444. (filter (negate already-used?) implicit-targets)))
  1445. (append explicit-binaries filtered-implicit-targets)))
  1446. (define* (find-rust-tests #:key (skipped-integration-tests %default-skipped-integration-tests)
  1447. #:allow-other-keys)
  1448. ;; This is like 'find-rust-binaries', but for tests.
  1449. (let* ((autotests? (package-autotests (manifest-package (*manifest*))))
  1450. (elaborate-target/skip* (cut elaborate-target/skip (*manifest*) <> 'test))
  1451. (explicit-tests
  1452. (filter-map elaborate-target/skip* (manifest-test (*manifest*))))
  1453. (implicit-tests
  1454. (if (and autotests? (directory-exists? "tests"))
  1455. (filter-map elaborate-target/skip* (scan-for-targets "tests"))
  1456. '()))
  1457. ;; XXX: duplicated from find-rust-binaries
  1458. (already-used?
  1459. (lambda (target)
  1460. (or (member (target-path target) (map target-path explicit-tests))
  1461. (member (target-name target) (map target-name explicit-tests)))))
  1462. (filtered-implicit-targets
  1463. (filter (negate already-used?) implicit-tests))
  1464. (allowed-test?
  1465. (lambda (target)
  1466. (not (member (target-name target) skipped-integration-tests)))))
  1467. (filter allowed-test? (append explicit-tests filtered-implicit-targets))))
  1468. (define (maybe-compile-target family target arguments)
  1469. ;; Check required-features.
  1470. (if (lset<= string=? (target-required-features target) (*features*))
  1471. (apply compile-binary-target target
  1472. #false ; maybe TODO?
  1473. #:family family arguments)
  1474. (begin (format #t "not compiling ~a, because the following features are missing: ~a~%"
  1475. target
  1476. (lset-difference string=?
  1477. (target-required-features target)
  1478. (*features*)))
  1479. #false)))
  1480. (define* (build-binaries #:rest arguments)
  1481. "Compile the Rust binaries described in Cargo.toml (but not examples, tests and benchmarks)."
  1482. (define (compile-binary-target* target)
  1483. (let ((destination (maybe-compile-target 'bin target arguments)))
  1484. (when destination
  1485. ;; Environment variable used by some tests, e.g. those of rust-asset-cmd@1.0.7.
  1486. ;; See: <https://doc.rust-lang.org/cargo/reference/environment-variables.html>.
  1487. (setenv (string-append "CARGO_BIN_EXE_" (target-name target))
  1488. destination))))
  1489. (for-each compile-binary-target* (apply find-rust-binaries arguments)))
  1490. (define* (build-tests #:key (tests? #false) #:allow-other-keys #:rest arguments)
  1491. "If TESTS? is true, build the 'integration tests' described in Cargo.toml."
  1492. (define compile-binary-target*
  1493. (cute maybe-compile-target 'test <>
  1494. (append (list #:integration-test? #true) arguments)))
  1495. (when tests?
  1496. (for-each compile-binary-target* (apply find-rust-tests arguments))))
  1497. ;; TODO: build-examples, build-benches.
  1498. (define* (load-manifest . rest)
  1499. "Parse Cargo.toml and save it in @code{*manifest*}."
  1500. (*manifest* (open-manifest "Cargo.toml" "Cargo.json")))
  1501. ;; rust-bzip2-sys has a 0.1.9+1.0.8 version string.
  1502. ;; Presumably CARGO_PKG_VERSION_MAJOR/MINOR/PATCH must be 0, 1, 9.
  1503. ;; TODO: what does PRE mean?
  1504. (define (without-plus version)
  1505. (match (string-split version #\+)
  1506. ((first . rest) first)))
  1507. ;; Set some variables that Cargo can set and that might
  1508. ;; be expected by build.rs. A (full?) list is avialable
  1509. ;; at <https://doc.rust-lang.org/cargo/reference/environment-variables.html>.
  1510. ;; When something does not appear in the Cargo.toml or such, according to
  1511. ;; that documentation, the environment variable needs to be set to the empty
  1512. ;; string.
  1513. (define* (set-platform-independent-manifest-variables
  1514. #:key (cargo-target-directory #false) #:allow-other-keys)
  1515. (define package (manifest-package (*manifest*)))
  1516. ;; Used by rust-cmake. TODO: actually set the various profile flags,
  1517. ;; optimisation levels, ...
  1518. (setenv "PROFILE" "release")
  1519. (setenv "DEBUG" "true")
  1520. (setenv "NUM_JOBS" (number->string (parallel-job-count)))
  1521. (let ((set-version-environment-variables
  1522. (lambda (major minor patch pre)
  1523. (setenv "CARGO_PKG_VERSION_MAJOR" major)
  1524. (setenv "CARGO_PKG_VERSION_MINOR" minor)
  1525. (setenv "CARGO_PKG_VERSION_PATCH" patch)
  1526. (setenv "CARGO_PKG_VERSION_PRE" pre))))
  1527. (match (string-split (without-plus (package-version package)) #\.)
  1528. ((major minor patch pre . rest) ; rest: unusual (non-existent?), but antioxidant doesn't care
  1529. (set-version-environment-variables major minor patch pre))
  1530. ((major minor patch)
  1531. (set-version-environment-variables major minor patch ""))
  1532. ((major minor)
  1533. (set-version-environment-variables major minor "" ""))
  1534. ((major)
  1535. (set-version-environment-variables major "" "" ""))
  1536. (() ; not set in Cargo.toml
  1537. (set-version-environment-variables "" "" "" ""))))
  1538. (setenv "CARGO_PKG_VERSION" (package-version package))
  1539. (setenv "CARGO_PKG_AUTHORS" (string-join (package-authors package) ":"))
  1540. (setenv "CARGO_PKG_NAME" (package-name package))
  1541. (setenv "CARGO_PKG_DESCRIPTION" (package-description package))
  1542. (setenv "CARGO_PKG_HOMEPAGE" (package-homepage package))
  1543. (setenv "CARGO_PKG_REPOSITORY" (package-repository package))
  1544. (setenv "CARGO_PKG_LICENSE" (package-license package))
  1545. (setenv "CARGO_PKG_LICENSE_FILE" (package-license-file package))
  1546. ;; According to Cargo, this is the directory for all ‘generated artifacts
  1547. ;; and intermediate files’ and defaults to a directory "target" in the working
  1548. ;; directory. However, in Guix, we want to install things in /gnu/store.
  1549. ;; It is also unclear what the file hierarchy is and which artifacts
  1550. ;; should be preserved in the store item and which should be removed.
  1551. ;;
  1552. ;; As such, don't set CARGO_TARGET_DIR by default and instead leave it
  1553. ;; to the packager to decide whether a cwd / store CARGO_TARGET_DIR is
  1554. ;; reasonable and what to preserve / remove.
  1555. ;;
  1556. ;; As an example, rust-cxx-build and newsboat make use of CARGO_TARGET_DIR.
  1557. (when cargo-target-directory
  1558. (let ((cargo-target-directory
  1559. (if (absolute-file-name? cargo-target-directory)
  1560. cargo-target-directory
  1561. (in-vicinity (getcwd) cargo-target-directory))))
  1562. (mkdir-p cargo-target-directory)
  1563. (setenv "CARGO_TARGET_DIR" cargo-target-directory))))
  1564. (define* (set-rust-environment-variables
  1565. #:key rust-environment-variables
  1566. #:allow-other-keys)
  1567. "Set environment variables like CARGO_CFG_TARGET_POINTER_WIDTH,
  1568. CARGO_CFG_TARGET_ARCH and RUSTC_BOOTSTRAP for which we do not need
  1569. package-specific information."
  1570. (for-each (match-lambda ((name . value) (setenv name value)))
  1571. rust-environment-variables)) ; TODO: maybe move more things inside
  1572. ;; Otherwise it looks for TARGET-strip even when compiling natively,
  1573. ;; due to how cross-compilation has been set up.
  1574. (define* (fixed-strip #:key target build #:allow-other-keys #:rest arguments)
  1575. (if (string=? target build)
  1576. (apply (assoc-ref %standard-phases 'strip)
  1577. (append arguments
  1578. (list #:target #false)))
  1579. (apply (assoc-ref %standard-phases 'strip) arguments)))
  1580. ;; Make sure there are not empty outputs (which can happen if, say,
  1581. ;; the crate doesn't come with benchmarks and for whatever reason
  1582. ;; no license file was installed.)
  1583. (define* (create-all-outputs #:key outputs #:allow-other-keys)
  1584. (define create-output
  1585. (match-lambda
  1586. ((label . file-name)
  1587. (unless (file-exists? file-name)
  1588. (mkdir file-name)))))
  1589. (for-each create-output outputs))
  1590. (define* (rust-tests-check #:key outputs tests?
  1591. (test-runner invoke)
  1592. (test-options '())
  1593. (parallel-tests? #true)
  1594. #:allow-other-keys)
  1595. "Look for tests in the 'tests' output and run them."
  1596. (when tests?
  1597. ;; rust-autocfg@1.0.1 wants a TESTS_TARGET_DIR. Can't directly
  1598. ;; find out what for.
  1599. (mkdir ".test-target-dir")
  1600. (setenv "TESTS_TARGET_DIR" (in-vicinity (getcwd) ".test-target-dir"))
  1601. (let ((where
  1602. (if (assoc-ref outputs "tests")
  1603. (string-append (assoc-ref outputs "tests") "/bin")
  1604. ".guix-tests")))
  1605. (for-each
  1606. (lambda (test)
  1607. ;; To help a little with debugging, show what's going on.
  1608. (format #t "Running ~a~%" test)
  1609. (apply test-runner test
  1610. `(,@(if parallel-tests?
  1611. `("--test-threads" ,(number->string (parallel-job-count)))
  1612. ;; The default for Rust is to do parallelism.
  1613. '("--test-threads" "1"))
  1614. ,@test-options)))
  1615. (find-files where)))))
  1616. (define (rust-tests-check/xorg . arguments)
  1617. "Run tests inside an environment with an X display server. This is often
  1618. required for graphical software."
  1619. ;; At least one build failed on ci.guix.gnu.org with
  1620. ;; ‘xvfb-run: error: Xvfb failed to start’. This was non-reproducible.
  1621. ;;
  1622. ;; Going by that web page, this can happen when another Xvfb from a previous
  1623. ;; xvfb-run did not exit yet. As sometimes multiple tests are run under
  1624. ;; xvfb-run (e.g. in the aforementioned build), add the proposed
  1625. ;; --auto-servernum.
  1626. (define (invoke/xorg . arguments)
  1627. (apply invoke "xvfb-run" "--auto-servernum" "--" arguments))
  1628. (apply rust-tests-check (append arguments (list #:test-runner invoke/xorg))))
  1629. (define (generate-cbindgen-metadata . arguments)
  1630. "Ggenerate the metadata as expected by cbindgen.
  1631. Not all fields are set, only the ones that seem to be required are set and even then
  1632. sometimes a dummy value suffices for now."
  1633. ;; Modifications can be tested against the rust-tectonic-... crates.
  1634. (define package (manifest-package (*manifest*)))
  1635. (define json-as-s-expression
  1636. `(("packages" .
  1637. #((("name" . ,(package-name package))
  1638. ("version" . ,(package-version package))
  1639. ("id" . "the package we are building")
  1640. ("source" . null)
  1641. ("dependencies" . #())
  1642. ("targets" . #((("kind" . #("lib"))
  1643. ("crate_types" . #("lib"))
  1644. ("name" . ,(package-name package))
  1645. ("src_path" . "src/lib.rs")))) ; TODO not true in general but sufficient for now
  1646. ("features")
  1647. ("manifest_path" . ,(in-vicinity (getcwd) "Cargo.toml")))))
  1648. ("workspace_members" . #("the package we are building"))
  1649. ("target_directory" . ,(getcwd)) ; TODO investigate proper valu
  1650. ("version" . ,1)
  1651. ("workspace_root" . ,(getcwd))))
  1652. (call-with-output-file ".cbindgen-metadata.json"
  1653. (cut scm->json json-as-s-expression <>
  1654. #:pretty #true); #:pretty: might help with debugging and doesn't cost much
  1655. #:encoding "UTF-8")
  1656. ;; This environment variable is used by rust-cbindgen-0.19-antioxidant-compatibility.patch.
  1657. (setenv "ANTIOXIDANT_CBINDGEN_METADATA" (in-vicinity (getcwd) ".cbindgen-metadata.json")))
  1658. (define* (setup-rustdoc #:key tests? #:allow-other-keys #:rest arguments)
  1659. ;; "rust-docmatic" runs 'rustdoc' in tests (in the tests of itself, and in the
  1660. ;; tests of dependencies). Make a wrapper of 'rustdoc' that adds appropriate
  1661. ;; arguments like --extern and -L.
  1662. (let* ((rustc-arguments
  1663. ;; Simulate a test compilation, to figure out appropriate flags.
  1664. (let/ec escape
  1665. (apply compile-binary-target
  1666. (elaborate-target
  1667. (*manifest*)
  1668. (scm->target
  1669. `(("name" . ".bogus-will-be-removed")
  1670. ("path" . ".bogus-will-be-removed"))))
  1671. #false
  1672. #:family 'test
  1673. #:integration-test? #true
  1674. #:invoke (lambda (rustc . arguments) (escape arguments))
  1675. arguments)))
  1676. ;; Remove inappropriate flags.
  1677. (filtered-arguments
  1678. (let loop ((remainder rustc-arguments))
  1679. (match remainder
  1680. (() '())
  1681. (("-C" (? (lambda (x)
  1682. (string-prefix? "metadata=" x))
  1683. metadata-argument) source . remainder) (loop remainder))
  1684. (("-o" ".guix-tests/.bogus-will-be-removed" . remainder) (loop remainder))
  1685. (("--crate-type=bin" . remainder) (loop remainder))
  1686. (("--test" . remainder) (loop remainder))
  1687. ;; "rustdoc" does not support linking to non-Rust libraries
  1688. (((? (cut string-prefix? "-l" <> )) . remainder) (loop remainder))
  1689. ((x . remainder) (cons x (loop remainder)))))))
  1690. ;; Make a wrapper. This assumes that quoting rules of shell are
  1691. ;; sufficiently close to the rules of Guile.
  1692. (mkdir ".guix-rustdoc-wrapper")
  1693. (call-with-output-file ".guix-rustdoc-wrapper/rustdoc"
  1694. (lambda (port)
  1695. (format port "#!~a~%exec -a \"$0\" ~s~{ ~s~} \"$@\""
  1696. (which "sh") (which "rustdoc") filtered-arguments)
  1697. ;; Make it executable.
  1698. (chmod port #o700)))
  1699. ;; Add the wrapped 'rustdoc' to $PATH.
  1700. (setenv "PATH" (string-append (getcwd) "/.guix-rustdoc-wrapper:"
  1701. (getenv "PATH")))))
  1702. (define %standard-antioxidant-phases
  1703. (modify-phases %standard-phases
  1704. ;; TODO: before configure?
  1705. (add-after 'unpack 'make-features-closure make-features-closure)
  1706. (add-after 'unpack 'choose-features choose-features)
  1707. (add-after 'unpack 'read-dependency-environment-variables read-dependency-environment-variables)
  1708. (add-after 'unpack 'set-platform-independent-manifest-variables
  1709. set-platform-independent-manifest-variables)
  1710. (add-after 'unpack 'set-rust-environment-variables set-rust-environment-variables)
  1711. (add-after 'unpack 'load-manifest load-manifest)
  1712. (add-after 'load-manifest 'generate-cbindgen-metadata
  1713. generate-cbindgen-metadata)
  1714. (replace 'configure configure)
  1715. (replace 'build build)
  1716. (add-before 'check 'setup-rustdoc setup-rustdoc)
  1717. ;; The non-test binaries need to be compiled before the tests
  1718. ;; as done here, otherwise the tests don't have access to
  1719. ;; CARGO_BIN_... at compile time.
  1720. (add-after 'build 'build-tests build-tests)
  1721. (add-after 'build 'build-binaries build-binaries)
  1722. (delete 'check)
  1723. (add-after 'install 'create-all-outputs create-all-outputs)
  1724. (replace 'strip fixed-strip)
  1725. ;; Some Rust packages (e.g. rust-os-pipe@0.9.2) want to access its binaries
  1726. ;; in the check phase.
  1727. (add-after 'strip 'check rust-tests-check)
  1728. (delete 'install))) ; TODO?