Commit | Line | Data |
---|---|---|
4b3cb7f4 | 1 | ;;; GNU Guix --- Functional package management for GNU |
2791870d | 2 | ;;; Copyright © 2013, 2014, 2015, 2016, 2019 Ludovic Courtès <ludo@gnu.org> |
4b3cb7f4 DC |
3 | ;;; Copyright © 2013 Andreas Enge <andreas@enge.fr> |
4 | ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org> | |
5 | ;;; Copyright © 2016 David Craven <david@craven.ch> | |
7d141788 | 6 | ;;; Copyright © 2019 Ivan Petkov <ivanppetkov@gmail.com> |
927c2518 | 7 | ;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net> |
4b3cb7f4 DC |
8 | ;;; |
9 | ;;; This file is part of GNU Guix. | |
10 | ;;; | |
11 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | |
12 | ;;; under the terms of the GNU General Public License as published by | |
13 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
14 | ;;; your option) any later version. | |
15 | ;;; | |
16 | ;;; GNU Guix is distributed in the hope that it will be useful, but | |
17 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;;; GNU General Public License for more details. | |
20 | ;;; | |
21 | ;;; You should have received a copy of the GNU General Public License | |
22 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
23 | ||
24 | (define-module (guix build-system cargo) | |
25 | #:use-module (guix search-paths) | |
26 | #:use-module (guix store) | |
27 | #:use-module (guix utils) | |
28 | #:use-module (guix derivations) | |
29 | #:use-module (guix packages) | |
30 | #:use-module (guix build-system) | |
31 | #:use-module (guix build-system gnu) | |
32 | #:use-module (ice-9 match) | |
a6ab6b78 IP |
33 | #:use-module (ice-9 vlist) |
34 | #:use-module (srfi srfi-1) | |
4b3cb7f4 | 35 | #:use-module (srfi srfi-26) |
7109e0ff | 36 | #:export (%cargo-build-system-modules |
7d141788 | 37 | %cargo-utils-modules |
7109e0ff | 38 | cargo-build-system |
2791870d | 39 | %crate-base-url |
4b3cb7f4 DC |
40 | crate-url |
41 | crate-url? | |
42 | crate-uri)) | |
43 | ||
2791870d LC |
44 | (define %crate-base-url |
45 | (make-parameter "https://crates.io")) | |
46 | (define crate-url | |
47 | (string-append (%crate-base-url) "/api/v1/crates/")) | |
48 | (define crate-url? | |
49 | (cut string-prefix? crate-url <>)) | |
4b3cb7f4 DC |
50 | |
51 | (define (crate-uri name version) | |
52 | "Return a URI string for the crate package hosted at crates.io corresponding | |
53 | to NAME and VERSION." | |
54 | (string-append crate-url name "/" version "/download")) | |
55 | ||
f342bb58 NM |
56 | (define (default-rust) |
57 | "Return the default Rust package." | |
4b3cb7f4 DC |
58 | ;; Lazily resolve the binding to avoid a circular dependency. |
59 | (let ((rust (resolve-interface '(gnu packages rust)))) | |
f342bb58 | 60 | (module-ref rust 'rust))) |
4b3cb7f4 | 61 | |
7d141788 IP |
62 | (define %cargo-utils-modules |
63 | ;; Build-side modules imported by default. | |
64 | `((guix build cargo-utils) | |
65 | ,@%gnu-build-system-modules)) | |
66 | ||
4b3cb7f4 DC |
67 | (define %cargo-build-system-modules |
68 | ;; Build-side modules imported by default. | |
69 | `((guix build cargo-build-system) | |
4fde0030 | 70 | (guix build json) |
7d141788 | 71 | ,@%cargo-utils-modules)) |
4b3cb7f4 DC |
72 | |
73 | (define* (cargo-build store name inputs | |
74 | #:key | |
75 | (tests? #t) | |
76 | (test-target #f) | |
1d3acde5 | 77 | (vendor-dir "guix-vendor") |
25fb58a3 | 78 | (cargo-build-flags ''("--release")) |
1d3acde5 | 79 | (cargo-test-flags ''("--release")) |
927c2518 | 80 | (features ''()) |
1d3acde5 | 81 | (skip-build? #f) |
4b3cb7f4 DC |
82 | (phases '(@ (guix build cargo-build-system) |
83 | %standard-phases)) | |
84 | (outputs '("out")) | |
85 | (search-paths '()) | |
86 | (system (%current-system)) | |
87 | (guile #f) | |
88 | (imported-modules %cargo-build-system-modules) | |
89 | (modules '((guix build cargo-build-system) | |
90 | (guix build utils)))) | |
91 | "Build SOURCE using CARGO, and with INPUTS." | |
92 | ||
93 | (define builder | |
94 | `(begin | |
95 | (use-modules ,@modules) | |
96 | (cargo-build #:name ,name | |
97 | #:source ,(match (assoc-ref inputs "source") | |
98 | (((? derivation? source)) | |
99 | (derivation->output-path source)) | |
100 | ((source) | |
101 | source) | |
102 | (source | |
103 | source)) | |
104 | #:system ,system | |
105 | #:test-target ,test-target | |
1d3acde5 | 106 | #:vendor-dir ,vendor-dir |
25fb58a3 | 107 | #:cargo-build-flags ,cargo-build-flags |
1d3acde5 | 108 | #:cargo-test-flags ,cargo-test-flags |
927c2518 | 109 | #:features ,features |
1d3acde5 IP |
110 | #:skip-build? ,skip-build? |
111 | #:tests? ,(and tests? (not skip-build?)) | |
4b3cb7f4 DC |
112 | #:phases ,phases |
113 | #:outputs %outputs | |
114 | #:search-paths ',(map search-path-specification->sexp | |
115 | search-paths) | |
116 | #:inputs %build-inputs))) | |
117 | ||
118 | (define guile-for-build | |
119 | (match guile | |
120 | ((? package?) | |
121 | (package-derivation store guile system #:graft? #f)) | |
122 | (#f ; the default | |
123 | (let* ((distro (resolve-interface '(gnu packages commencement))) | |
124 | (guile (module-ref distro 'guile-final))) | |
125 | (package-derivation store guile system #:graft? #f))))) | |
126 | ||
127 | (build-expression->derivation store name builder | |
128 | #:inputs inputs | |
129 | #:system system | |
130 | #:modules imported-modules | |
d608e231 | 131 | #:outputs outputs |
4b3cb7f4 DC |
132 | #:guile-for-build guile-for-build)) |
133 | ||
a6ab6b78 IP |
134 | (define (package-cargo-inputs p) |
135 | (apply | |
136 | (lambda* (#:key (cargo-inputs '()) #:allow-other-keys) | |
137 | cargo-inputs) | |
138 | (package-arguments p))) | |
139 | ||
140 | (define (package-cargo-development-inputs p) | |
141 | (apply | |
142 | (lambda* (#:key (cargo-development-inputs '()) #:allow-other-keys) | |
143 | cargo-development-inputs) | |
144 | (package-arguments p))) | |
145 | ||
146 | (define (crate-closure inputs) | |
147 | "Return the closure of INPUTS when considering the 'cargo-inputs' and | |
148 | 'cargod-dev-deps' edges. Omit duplicate inputs, except for those | |
149 | already present in INPUTS itself. | |
150 | ||
151 | This is implemented as a breadth-first traversal such that INPUTS is | |
152 | preserved, and only duplicate extracted inputs are removed. | |
153 | ||
154 | Forked from ((guix packages) transitive-inputs) since this extraction | |
155 | uses slightly different rules compared to the rest of Guix (i.e. we | |
156 | do not extract the conventional inputs)." | |
157 | (define (seen? seen item) | |
158 | ;; FIXME: We're using pointer identity here, which is extremely sensitive | |
159 | ;; to memoization in package-producing procedures; see | |
160 | ;; <https://bugs.gnu.org/30155>. | |
161 | (vhash-assq item seen)) | |
162 | ||
163 | (let loop ((inputs inputs) | |
164 | (result '()) | |
165 | (propagated '()) | |
166 | (first? #t) | |
167 | (seen vlist-null)) | |
168 | (match inputs | |
169 | (() | |
170 | (if (null? propagated) | |
171 | (reverse result) | |
172 | (loop (reverse (concatenate propagated)) result '() #f seen))) | |
173 | (((and input (label (? package? package))) rest ...) | |
174 | (if (and (not first?) (seen? seen package)) | |
175 | (loop rest result propagated first? seen) | |
176 | (loop rest | |
177 | (cons input result) | |
178 | (cons (package-cargo-inputs package) | |
179 | propagated) | |
180 | first? | |
181 | (vhash-consq package package seen)))) | |
182 | ((input rest ...) | |
183 | (loop rest (cons input result) propagated first? seen))))) | |
184 | ||
185 | (define (expand-crate-sources cargo-inputs cargo-development-inputs) | |
186 | "Extract all transitive sources for CARGO-INPUTS and CARGO-DEVELOPMENT-INPUTS | |
187 | along their 'cargo-inputs' edges. | |
188 | ||
189 | Cargo requires all transitive crate dependencies' sources to be available | |
190 | in its index, even if they are optional (this is so it can generate | |
191 | deterministic Cargo.lock files regardless of the target platform or enabled | |
192 | features). Thus we need all transitive crate dependencies for any cargo | |
193 | dev-dependencies, but this is only needed when building/testing a crate directly | |
194 | (i.e. we will never need transitive dev-dependencies for any dependency crates). | |
195 | ||
196 | Another complication arises due potential dependency cycles from Guix's | |
197 | perspective: Although cargo does not permit cyclic dependencies between crates, | |
198 | however, it permits cycles to occur via dev-dependencies. For example, if crate | |
199 | X depends on crate Y, crate Y's tests could pull in crate X to to verify | |
200 | everything builds properly (this is a rare scenario, but it it happens for | |
201 | example with the `proc-macro2` and `quote` crates). This is allowed by cargo | |
202 | because tests are built as a pseudo-crate which happens to depend on the | |
203 | X and Y crates, forming an acyclic graph. | |
204 | ||
205 | We can side step this problem by only considering regular cargo dependencies | |
206 | since they are guaranteed to not have cycles. We can further resolve any | |
207 | potential dev-dependency cycles by extracting package sources (which never have | |
208 | any dependencies and thus no cycles can exist). | |
209 | ||
210 | There are several implications of this decision: | |
211 | * Building a package definition does not require actually building/checking | |
212 | any dependent crates. This can be a benefits: | |
213 | - For example, sometimes a crate may have an optional dependency on some OS | |
214 | specific package which cannot be built or run on the current system. This | |
215 | approach means that the build will not fail if cargo ends up internally ignoring | |
216 | the dependency. | |
217 | - It avoids waiting for quadratic builds from source: cargo always builds | |
218 | dependencies within the current workspace. This is largely due to Rust not | |
219 | having a stable ABI and other resolutions that cargo applies. This means that | |
220 | if we have a depencency chain of X -> Y -> Z and we build each definition | |
221 | independently the following will happen: | |
222 | * Cargo will build and test crate Z | |
223 | * Cargo will build crate Z in Y's workspace, then build and test Y | |
224 | * Cargo will build crates Y and Z in X's workspace, then build and test X | |
225 | * But there are also some downsides with this approach: | |
226 | - If a dependent crate is subtly broken on the system (i.e. it builds but its | |
227 | tests fail) the consuming crates may build and test successfully but | |
228 | actually fail during normal usage (however, the CI will still build all | |
229 | packages which will give visibility in case packages suddenly break). | |
230 | - Because crates aren't declared as regular inputs, other Guix facilities | |
231 | such as tracking package graphs may not work by default (however, this is | |
232 | something that can always be extended or reworked in the future)." | |
233 | (filter-map | |
234 | (match-lambda | |
235 | ((label (? package? p)) | |
236 | (list label (package-source p))) | |
237 | ((label input) | |
238 | (list label input))) | |
239 | (crate-closure (append cargo-inputs cargo-development-inputs)))) | |
240 | ||
4b3cb7f4 DC |
241 | (define* (lower name |
242 | #:key source inputs native-inputs outputs system target | |
f342bb58 | 243 | (rust (default-rust)) |
a6ab6b78 IP |
244 | (cargo-inputs '()) |
245 | (cargo-development-inputs '()) | |
4b3cb7f4 DC |
246 | #:allow-other-keys |
247 | #:rest arguments) | |
248 | "Return a bag for NAME." | |
249 | ||
250 | (define private-keywords | |
a6ab6b78 IP |
251 | '(#:source #:target #:rust #:inputs #:native-inputs #:outputs |
252 | #:cargo-inputs #:cargo-development-inputs)) | |
4b3cb7f4 DC |
253 | |
254 | (and (not target) ;; TODO: support cross-compilation | |
255 | (bag | |
256 | (name name) | |
257 | (system system) | |
258 | (target target) | |
259 | (host-inputs `(,@(if source | |
260 | `(("source" ,source)) | |
261 | '()) | |
262 | ,@inputs | |
263 | ||
264 | ;; Keep the standard inputs of 'gnu-build-system' | |
265 | ,@(standard-packages))) | |
f342bb58 NM |
266 | (build-inputs `(("cargo" ,rust "cargo") |
267 | ("rustc" ,rust) | |
a6ab6b78 | 268 | ,@(expand-crate-sources cargo-inputs cargo-development-inputs) |
4b3cb7f4 DC |
269 | ,@native-inputs)) |
270 | (outputs outputs) | |
271 | (build cargo-build) | |
272 | (arguments (strip-keyword-arguments private-keywords arguments))))) | |
273 | ||
274 | (define cargo-build-system | |
275 | (build-system | |
276 | (name 'cargo) | |
277 | (description | |
278 | "Cargo build system, to build Rust crates") | |
279 | (lower lower))) |