diff mbox

[1/7] build-system: Add cargo build system.

Message ID 20160928151538.11679-1-david@craven.ch
State New
Headers show

Commit Message

David Craven Sept. 28, 2016, 3:15 p.m. UTC
* guix/build-system/cargo.scm (default-cargo, default-rustc,
  %cargo-build-system-modules, cargo-build, lower, cargo-build-system):
  New variables.
* guix/build/cargo-build-system.scm (configure, build, check, install,
  %standard-phases, cargo-build): New variables.
---
 guix/build-system/cargo.scm       | 149 ++++++++++++++++++++++++++++++++++++++
 guix/build/cargo-build-system.scm |  81 +++++++++++++++++++++
 2 files changed, 230 insertions(+)
 create mode 100644 guix/build-system/cargo.scm
 create mode 100644 guix/build/cargo-build-system.scm

Comments

Ricardo Wurmus Sept. 30, 2016, 7:21 a.m. UTC | #1
David Craven <david@craven.ch> writes:

> * guix/build-system/cargo.scm (default-cargo, default-rustc,
>   %cargo-build-system-modules, cargo-build, lower, cargo-build-system):
>   New variables.
> * guix/build/cargo-build-system.scm (configure, build, check, install,
>   %standard-phases, cargo-build): New variables.

Since these are new files you can just write

* guix/build-system/cargo.scm: New file.
* guix/build/cargo-build-system.scm: New file.

But you should also add them to “gnu/local.mk”.

> +(define (system->rust-platform system)
> +  (cond
> +   ((string-prefix? "x86_64" system) "x86_64-unknown-linux-gnu")
> +   ((string-prefix? "i686" system) "i686-unknown-linux-gnu")))

Is there no rust for other systems?  How about adding a default case, or
are you ensuring that system can only have one of these two values?

> +(define* (cargo-build store name inputs
> +                      #:key
> +                      (tests? #t)
> +                      (test-target #f)

Is this correct?  What happens when test-target is not specified?  I see
below that the target is hard-coded to “test”.  Maybe set it to “test”
here and use it on the build side?

> diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm
> new file mode 100644
> index 0000000..abe7e05
> --- /dev/null
> +++ b/guix/build/cargo-build-system.scm
> @@ -0,0 +1,81 @@
> +;;; GNU Guix --- Functional package management for GNU
> +;;; Copyright © 2016 David Craven <david@craven.ch>
> +;;;
> +;;; This file is part of GNU Guix.
> +;;;
> +;;; GNU Guix is free software; you can redistribute it and/or modify it
> +;;; under the terms of the GNU General Public License as published by
> +;;; the Free Software Foundation; either version 3 of the License, or (at
> +;;; your option) any later version.
> +;;;
> +;;; GNU Guix is distributed in the hope that it will be useful, but
> +;;; WITHOUT ANY WARRANTY; without even the implied warranty of
> +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +;;; GNU General Public License for more details.
> +;;;
> +;;; You should have received a copy of the GNU General Public License
> +;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
> +
> +(define-module (guix build cargo-build-system)
> +  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
> +  #:use-module (guix build utils)
> +  #:use-module (ice-9 match)
> +  #:use-module (ice-9 ftw)
> +  #:use-module (srfi srfi-1)
> +  #:use-module (srfi srfi-26)
> +  #:export (%standard-phases
> +            cargo-build))
> +
> +;; Commentary:
> +;;
> +;; Builder-side code of the standard Rust package build procedure.
> +;;
> +;; Code:
> +
> +(define* (configure #:rest _)
> +  "Replace Cargo.toml [dependencies] section with guix inputs."
> +  ;; TODO
> +  #t)

I don’t understand this.  Does this mean that currently dependencies are
always bundled?

> +(define* (build #:key (cargo-build-flags '("--release")) #:allow-other-keys)
> +  "Build a given Cargo package."
> +  (zero? (apply system* `("cargo" "build" ,@cargo-build-flags))))
> +
> +(define* (check #:rest _)
> +  "Run tests for a given Cargo package."
> +  (zero? (system* "cargo" "test")))

Shouldn’t this respect “test-target”?

> +
> +(define* (install #:key inputs outputs #:allow-other-keys)
> +  "Install a given Cargo package."
> +  (let* ((out (assoc-ref outputs "out"))
> +         (src (assoc-ref inputs "source"))
> +         (bin (string-append out "/bin"))
> +         (rsrc (string-append out "/rustsrc")))
> +    (mkdir-p rsrc)
> +    ;; Rust doesn't have a stable ABI yet. Because of this
> +    ;; Cargo doesn't have a search path for binaries yet.
> +    ;; Until this changes we are working around this by
> +    ;; distributing crates as source and replacing
> +    ;; references in Cargo.toml with store paths.
> +    (copy-recursively "src" (string-append rsrc "/src"))
> +    (install-file "Cargo.toml" rsrc)

I’m not familiar with Rust so I don’t know what crates are.  Are they
actually source files?  Are they archives?

You write that we are replacing references in Cargo.toml with store
paths but I see no evidence of this.  Could you please clarify?

> +    ;; When the package includes executables we install
> +    ;; it using cargo install. This fails when the crate
> +    ;; doesn't contain an executable.
> +    (system* "cargo" "install" "--root" bin)
> +    #t))

Why only use “cargo install” in case there are executables?  Can we
detect this by looking at some description file of the package?  It
doesn’t seem right to unconditionally end on #t.

~~ Ricardo
David Craven Sept. 30, 2016, 10:49 a.m. UTC | #2
> Is this correct?  What happens when test-target is not specified?  I see
> below that the target is hard-coded to “test”.  Maybe set it to “test”
> here and use it on the build side?

> Shouldn’t this respect “test-target”?

There is no other build target than test. Functions that are tests
have an attribute #[cfg(test)] above them. Running cargo test tells
cargo to run all functions that are annotated like that.

> I’m not familiar with Rust so I don’t know what crates are.  Are they
> actually source files?  Are they archives?

A crate is a tar.gz file which contains the source.

> You write that we are replacing references in Cargo.toml with store
> paths but I see no evidence of this.  Could you please clarify?

These patches only build packages without dependencies. I have
packages importing/building with a simple dependency structure.

I'm refactoring the importer to allow recursive imports, and then I'll
try to package cargo with it.

> Why only use “cargo install” in case there are executables?  Can we
> detect this by looking at some description file of the package?  It
> doesn’t seem right to unconditionally end on #t.

So we installed the source. At this point the build system can use it. The
only time we install binaries is when the crate contains an executable or
a script. In those cases `cargo install` should work. If there isn't an
executable we don't care if it fails.

> I don’t understand this.  Does this mean that currently dependencies are
> always bundled?

No. This is to prevent cargo from fetching the missing dependencies from
crates.io and to use our crates. Rust/LLVM performs aggressive optimization,
executables include all their rust symbols. You can write a C
compatible library,
but most crates are meant for Rust development. They have their own binary
format called *.rlib that is like an *.a file. The main focus for now
is building
cargo. The situation should improve, but this is how other distros are packaging
crates currently.
Ludovic Courtès Oct. 4, 2016, 9:03 a.m. UTC | #3
David Craven <david@craven.ch> skribis:

> * guix/build-system/cargo.scm (default-cargo, default-rustc,
>   %cargo-build-system-modules, cargo-build, lower, cargo-build-system):
>   New variables.
> * guix/build/cargo-build-system.scm (configure, build, check, install,
>   %standard-phases, cargo-build): New variables.

[...]

> +(define* (install #:key inputs outputs #:allow-other-keys)
> +  "Install a given Cargo package."
> +  (let* ((out (assoc-ref outputs "out"))
> +         (src (assoc-ref inputs "source"))
> +         (bin (string-append out "/bin"))
> +         (rsrc (string-append out "/rustsrc")))
> +    (mkdir-p rsrc)
> +    ;; Rust doesn't have a stable ABI yet. Because of this
> +    ;; Cargo doesn't have a search path for binaries yet.
> +    ;; Until this changes we are working around this by
> +    ;; distributing crates as source and replacing
> +    ;; references in Cargo.toml with store paths.
> +    (copy-recursively "src" (string-append rsrc "/src"))

OK.  In
<https://lists.gnu.org/archive/html/guix-devel/2016-09/msg01991.html>, I
suggested using ‘share/rust-source’ or similar to store the source.
Would it be possible?

Last thing: Could you add a couple of lines in guix.texi under “Build
Systems”?

OK with these changes.

Thanks!

Ludo’.
ng0 Dec. 9, 2016, 12:17 p.m. UTC | #4
Ludovic Courtès <ludo@gnu.org> writes:

> David Craven <david@craven.ch> skribis:
>
>> * guix/build-system/cargo.scm (default-cargo, default-rustc,
>>   %cargo-build-system-modules, cargo-build, lower, cargo-build-system):
>>   New variables.
>> * guix/build/cargo-build-system.scm (configure, build, check, install,
>>   %standard-phases, cargo-build): New variables.
>
> [...]
>
>> +(define* (install #:key inputs outputs #:allow-other-keys)
>> +  "Install a given Cargo package."
>> +  (let* ((out (assoc-ref outputs "out"))
>> +         (src (assoc-ref inputs "source"))
>> +         (bin (string-append out "/bin"))
>> +         (rsrc (string-append out "/rustsrc")))
>> +    (mkdir-p rsrc)
>> +    ;; Rust doesn't have a stable ABI yet. Because of this
>> +    ;; Cargo doesn't have a search path for binaries yet.
>> +    ;; Until this changes we are working around this by
>> +    ;; distributing crates as source and replacing
>> +    ;; references in Cargo.toml with store paths.
>> +    (copy-recursively "src" (string-append rsrc "/src"))
>
> OK.  In
> <https://lists.gnu.org/archive/html/guix-devel/2016-09/msg01991.html>, I
> suggested using ‘share/rust-source’ or similar to store the source.
> Would it be possible?
>
> Last thing: Could you add a couple of lines in guix.texi under “Build
> Systems”?
>
> OK with these changes.
>
> Thanks!
>
> Ludo’.
>
>
Hi, would it be possible to add some very short notes on why this
isn't complete from your (David) view?

Once you have some time to go into detail you could add further
explanations, for now an tl;dr would get great to help fixing
it up. If that's not possible just see this as an reminder to do
this when you have the time :)

Thanks for your work on this!
Ludovic Courtès Dec. 9, 2016, 2:17 p.m. UTC | #5
ng0 <ng0@libertad.pw> skribis:

> Ludovic Courtès <ludo@gnu.org> writes:
>
>> David Craven <david@craven.ch> skribis:
>>
>>> * guix/build-system/cargo.scm (default-cargo, default-rustc,
>>>   %cargo-build-system-modules, cargo-build, lower, cargo-build-system):
>>>   New variables.
>>> * guix/build/cargo-build-system.scm (configure, build, check, install,
>>>   %standard-phases, cargo-build): New variables.
>>
>> [...]
>>
>>> +(define* (install #:key inputs outputs #:allow-other-keys)
>>> +  "Install a given Cargo package."
>>> +  (let* ((out (assoc-ref outputs "out"))
>>> +         (src (assoc-ref inputs "source"))
>>> +         (bin (string-append out "/bin"))
>>> +         (rsrc (string-append out "/rustsrc")))
>>> +    (mkdir-p rsrc)
>>> +    ;; Rust doesn't have a stable ABI yet. Because of this
>>> +    ;; Cargo doesn't have a search path for binaries yet.
>>> +    ;; Until this changes we are working around this by
>>> +    ;; distributing crates as source and replacing
>>> +    ;; references in Cargo.toml with store paths.
>>> +    (copy-recursively "src" (string-append rsrc "/src"))
>>
>> OK.  In
>> <https://lists.gnu.org/archive/html/guix-devel/2016-09/msg01991.html>, I
>> suggested using ‘share/rust-source’ or similar to store the source.
>> Would it be possible?
>>
>> Last thing: Could you add a couple of lines in guix.texi under “Build
>> Systems”?
>>
>> OK with these changes.
>>
>> Thanks!
>>
>> Ludo’.
>>
>>
> Hi, would it be possible to add some very short notes on why this
> isn't complete from your (David) view?

Given the work that has gone into these Rust patches, I think it would
be nice to apply them and possibly document any shortcoming or future
work items.

WDYT, David?  :-)

Ludo’.
David Craven Dec. 11, 2016, 7:47 p.m. UTC | #6
Hi Ludo!

> Given the work that has gone into these Rust patches, I think it would
> be nice to apply them and possibly document any shortcoming or future
> work items.

I went over all your previous emails and fixed your previous comments.
Those aren't part of the new patch series yet...

In summary those changes are:
* add (supported-systems '("x86_64-linux")
* Improve rustc-bootstrap, cargo-bootstrap and rust-bootstrap synopsis
and description
* add crate updater to table in guix.texi
* add Eric to the rust.scm file


Some unanswered questions:

guix size of rust-bootstrap shows a size of zero. Are propagated
inputs not part of this measurement?

Now there are two different gcc:lib's in the dependency graph of
rustc-bootstrap. Can I use the system gcc:lib? Does gcc "lib" need to
be added to %final-inputs in commencement.scm?

To build rustc i686 and x86_64 with the same binary, rustc-bootstrap
needs to find the 32bit glibc dynamic linker. How do I get a 32bit
glibc for that?


What needs to be done:

* the updater patch needs to be looked at. it fixed the packages not
updating, I need to find out why that was and/or if it still is the
case.

* Crate names need to be saved as a property and propagated to the
build system when the crate name contains underscores.

* Put crate source in OUT/share/rust-source/PACKAGE-VERSION

* Cargo build system should not do anything if there isn't a
Cargo.lock file except copy the source to out.

* Finish the recursive importer. The recursive importer should use the
Cargo.lock file to get the right package versions as
rust-PACKAGENAME-VERSION. If a crate doesn't have a Cargo.lock only
import the latest version non-recursively as rust-PACKAGENAME.

* Get cargo to build

* Make the updater update package versions recursively

* Improve cargo bootstrapping story.



Hope I didn't forget anything...
David
Ludovic Courtès Dec. 13, 2016, 5:15 p.m. UTC | #7
Hi David!

David Craven <david@craven.ch> skribis:

>> Given the work that has gone into these Rust patches, I think it would
>> be nice to apply them and possibly document any shortcoming or future
>> work items.
>
> I went over all your previous emails and fixed your previous comments.
> Those aren't part of the new patch series yet...
>
> In summary those changes are:
> * add (supported-systems '("x86_64-linux")
> * Improve rustc-bootstrap, cargo-bootstrap and rust-bootstrap synopsis
> and description
> * add crate updater to table in guix.texi
> * add Eric to the rust.scm file

OK.

> Some unanswered questions:
>
> guix size of rust-bootstrap shows a size of zero. Are propagated
> inputs not part of this measurement?

No they’re not; it’s only the things that show in in ‘guix gc -R
rust-bootstrap’.

> Now there are two different gcc:lib's in the dependency graph of
> rustc-bootstrap. Can I use the system gcc:lib? Does gcc "lib" need to
> be added to %final-inputs in commencement.scm?

Hmm why is there a second one?  ‘gnu-build-system’ already provides gcc
and gcc:lib as implicit inputs, so maybe it’s just a matter of removing
‘gcc’ from Rust’s ‘inputs’?

> To build rustc i686 and x86_64 with the same binary, rustc-bootstrap
> needs to find the 32bit glibc dynamic linker. How do I get a 32bit
> glibc for that?

When you do ‘guix build rust -s i686-linux’, you get 32-bit packages.
Is that enough or am I missing something?  :-)

> What needs to be done:
>
> * the updater patch needs to be looked at. it fixed the packages not
> updating, I need to find out why that was and/or if it still is the
> case.
>
> * Crate names need to be saved as a property and propagated to the
> build system when the crate name contains underscores.
>
> * Put crate source in OUT/share/rust-source/PACKAGE-VERSION
>
> * Cargo build system should not do anything if there isn't a
> Cargo.lock file except copy the source to out.
>
> * Finish the recursive importer. The recursive importer should use the
> Cargo.lock file to get the right package versions as
> rust-PACKAGENAME-VERSION. If a crate doesn't have a Cargo.lock only
> import the latest version non-recursively as rust-PACKAGENAME.
>
> * Get cargo to build
>
> * Make the updater update package versions recursively
>
> * Improve cargo bootstrapping story.

OK, thanks for the detailed update!

Ludo’.
David Craven Dec. 13, 2016, 6:07 p.m. UTC | #8
> Hmm why is there a second one?  ‘gnu-build-system’ already provides gcc
> and gcc:lib as implicit inputs, so maybe it’s just a matter of removing
> ‘gcc’ from Rust’s ‘inputs’?

So we do have an implicit gcc:lib package as an input. But I have to patchelf
the rustc binaries and libraries so I need a way to retrieve the gcc lib output.
I can do (assoc-ref inputs "gcc") but how do I get a reference to the
lib output?
(assoc-ref* inputs "gcc" "lib") doesn't work... So I add ("gcc:lib"
,gcc "lib") explicitly
to the inputs so that I can retrieve it. This results in slightly
different versions
being included.

guix size rustc-bootstrap
/gnu/store/fafzikyzhwbryqxxqsbsdkkq47rfrd8z-rustc-bootstrap-1.12.1
414.6   240.4  58.0%
/gnu/store/y1g6991kxvdk4vxhsq07r5saww30v8dq-gcc-4.9.4
138.6    77.2  18.6%
/gnu/store/a64w9dq219a8d9k4mfz76mnzph9wsvfj-zlib-1.2.8
61.3     0.4   0.1%
/gnu/store/q4a1z2arssij8iq80i4k0zqallqlh2lv-gcc-4.9.4-lib
61.1    22.8   5.5%
/gnu/store/cdi08kw7r6r684w8mk0xq0dkgpjhfpmd-gcc-4.9.4-lib
61.0    22.7   5.5%
/gnu/store/qkw4zrwfybxww8f56nkb6hggxambk89b-bash-4.4.0
50.7     5.4   1.3%
/gnu/store/bm0gfw4jkw8gd0vpnnzrb6z0xncrbx3p-readline-7.0
45.3     1.3   0.3%
/gnu/store/hdrli1v7q3107w842s7di8rid82xlfvl-ncurses-6.0
44.0     5.7   1.4%
/gnu/store/vxdm2dqckv3yvwihr4hs6f886v6104az-zlib-1.2.8
38.6     0.4   0.1%
/gnu/store/iwgi9001dmmihrjg4rqhd6pa6788prjw-glibc-2.24
38.3    36.8   8.9%
/gnu/store/rvgmixpmsq5lqr9qflhkm70kg7a4rys2-bash-static-4.4.0
1.4     1.4   0.3%
total: 414.6 MiB

I also don't know why there are two zlib's in there.

guix gc --referrers /gnu/store/vxdm2dqckv3yvwihr4hs6f886v6104az-zlib-1.2.8
/gnu/store/vxdm2dqckv3yvwihr4hs6f886v6104az-zlib-1.2.8
/gnu/store/y1g6991kxvdk4vxhsq07r5saww30v8dq-gcc-4.9.4

guix gc --referrers /gnu/store/q4a1z2arssij8iq80i4k0zqallqlh2lv-gcc-4.9.4-lib
/gnu/store/59fld0ah99fn7fc5vfsn51dm4b7x401g-clang-3.8.1
/gnu/store/d5vl2fljy7vlhz51yccw8y76yw8rzjrc-cargo-bootstrap-0.13.0
/gnu/store/fafzikyzhwbryqxxqsbsdkkq47rfrd8z-rustc-bootstrap-1.12.1
/gnu/store/q4a1z2arssij8iq80i4k0zqallqlh2lv-gcc-4.9.4-lib

Maybe it doesn't matter too much, I was just curious that's all :)

> When you do ‘guix build rust -s i686-linux’, you get 32-bit packages.
> Is that enough or am I missing something?

The problem here is that we are patching precompiled binaries for i686 to get
them to run with guix. So I need to get a reference to a 32-bit glibc
from within the package definition. I think that we'd have to --enable-multilib.
I don't know if we can make it a separate output and how we can prevent it
from being added to %final-inputs by default. And then we need a way of
obtaining it from within a package definition which amounts to the same
problem as with gcc:lib.
Ludovic Courtès Dec. 13, 2016, 10:11 p.m. UTC | #9
David Craven <david@craven.ch> skribis:

>> Hmm why is there a second one?  ‘gnu-build-system’ already provides gcc
>> and gcc:lib as implicit inputs, so maybe it’s just a matter of removing
>> ‘gcc’ from Rust’s ‘inputs’?
>
> So we do have an implicit gcc:lib package as an input. But I have to patchelf
> the rustc binaries and libraries so I need a way to retrieve the gcc lib output.
> I can do (assoc-ref inputs "gcc") but how do I get a reference to the
> lib output?
> (assoc-ref* inputs "gcc" "lib") doesn't work... So I add ("gcc:lib"
> ,gcc "lib") explicitly
> to the inputs so that I can retrieve it. This results in slightly
> different versions
> being included.

OK, got it.

Then you should probably be able to do:

  (inputs `(("gcc:lib" ,(canonical-package gcc) "lib") …))

>> When you do ‘guix build rust -s i686-linux’, you get 32-bit packages.
>> Is that enough or am I missing something?
>
> The problem here is that we are patching precompiled binaries for i686 to get
> them to run with guix. So I need to get a reference to a 32-bit glibc
> from within the package definition. I think that we'd have to --enable-multilib.
> I don't know if we can make it a separate output and how we can prevent it
> from being added to %final-inputs by default. And then we need a way of
> obtaining it from within a package definition which amounts to the same
> problem as with gcc:lib.

We don’t currently --enable-multilib, but you could force a reference to
a 32-bit libc with this hack:

  (inputs `(("glibc32" ,(package (inherit glibc)
                          (arguments '(#:system "i686-linux" …))))))

That’s unnecessary when the system is already "i686-linux", of course.

Would that help?

Cheers,
Ludo’.
David Craven Dec. 14, 2016, 11:48 a.m. UTC | #10
> Would that help?

Awesome! Thank you.

The bootstrapping solution turned out much better. I added the
#:system argument to rustc-bootstrap and cargo-bootstrap and moved the
gcc -> cc symlink into rust-bootstrap, so that we get a native
toolchain. We can build rustc on i686 and x86_64 using the same
binary.
Ludovic Courtès Dec. 15, 2016, 3:45 p.m. UTC | #11
David Craven <david@craven.ch> skribis:

>> Would that help?
>
> Awesome! Thank you.
>
> The bootstrapping solution turned out much better. I added the
> #:system argument to rustc-bootstrap and cargo-bootstrap and moved the
> gcc -> cc symlink into rust-bootstrap, so that we get a native
> toolchain. We can build rustc on i686 and x86_64 using the same
> binary.

Cool, thanks!

Ludo'.
diff mbox

Patch

diff --git a/guix/build-system/cargo.scm b/guix/build-system/cargo.scm
new file mode 100644
index 0000000..84cb644
--- /dev/null
+++ b/guix/build-system/cargo.scm
@@ -0,0 +1,149 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
+;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
+;;; Copyright © 2016 David Craven <david@craven.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build-system cargo)
+  #:use-module (guix search-paths)
+  #:use-module (guix store)
+  #:use-module (guix utils)
+  #:use-module (guix derivations)
+  #:use-module (guix packages)
+  #:use-module (guix build-system)
+  #:use-module (guix build-system gnu)
+  #:use-module (ice-9 match)
+  #:export (cargo-build-system
+            crate-uri
+            system->rust-platform))
+
+(define (crate-uri name version)
+  "Return a URI string for the crate package hosted at crates.io corresponding
+to NAME and VERSION."
+  (string-append "https://crates.io/api/v1/crates/" name "/" version "/download"))
+
+(define (system->rust-platform system)
+  (cond
+   ((string-prefix? "x86_64" system) "x86_64-unknown-linux-gnu")
+   ((string-prefix? "i686" system) "i686-unknown-linux-gnu")))
+
+(define (default-cargo)
+  "Return the default Cargo package."
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (let ((rust (resolve-interface '(gnu packages rust))))
+    (module-ref rust 'cargo-bootstrap)))
+
+(define (default-rustc)
+  "Return the default Rustc package."
+  ;; Lazily resolve the binding to avoid a circular dependency.
+  (let ((rust (resolve-interface '(gnu packages rust))))
+    (module-ref rust 'rustc-bootstrap)))
+
+(define %cargo-build-system-modules
+  ;; Build-side modules imported by default.
+  `((guix build cargo-build-system)
+    ,@%gnu-build-system-modules))
+
+(define* (cargo-build store name inputs
+                      #:key
+                      (tests? #t)
+                      (test-target #f)
+                      (configure-flags #f)
+                      (phases '(@ (guix build cargo-build-system)
+                                  %standard-phases))
+                      (outputs '("out"))
+                      (search-paths '())
+                      (system (%current-system))
+                      (guile #f)
+                      (imported-modules %cargo-build-system-modules)
+                      (modules '((guix build cargo-build-system)
+                                 (guix build utils))))
+  "Build SOURCE using CARGO, and with INPUTS."
+
+  (define builder
+    `(begin
+       (use-modules ,@modules)
+       (cargo-build #:name ,name
+                    #:source ,(match (assoc-ref inputs "source")
+                                (((? derivation? source))
+                                 (derivation->output-path source))
+                                ((source)
+                                 source)
+                                (source
+                                 source))
+                    #:system ,system
+                    #:test-target ,test-target
+                    #:tests? ,tests?
+                    #:phases ,phases
+                    #:outputs %outputs
+                    #:search-paths ',(map search-path-specification->sexp
+                                          search-paths)
+                    #:inputs %build-inputs)))
+
+  (define guile-for-build
+    (match guile
+      ((? package?)
+       (package-derivation store guile system #:graft? #f))
+      (#f                                         ; the default
+       (let* ((distro (resolve-interface '(gnu packages commencement)))
+              (guile  (module-ref distro 'guile-final)))
+         (package-derivation store guile system #:graft? #f)))))
+
+  (build-expression->derivation store name builder
+                                #:inputs inputs
+                                #:system system
+                                #:modules imported-modules
+                                #:outputs outputs
+                                #:guile-for-build guile-for-build))
+
+(define* (lower name
+                #:key source inputs native-inputs outputs system target
+                (cargo (default-cargo))
+                (rustc (default-rustc))
+                #:allow-other-keys
+                #:rest arguments)
+  "Return a bag for NAME."
+
+  (define private-keywords
+    '(#:source #:target #:cargo #:rustc #:inputs #:native-inputs))
+
+  (and (not target) ;; TODO: support cross-compilation
+       (bag
+         (name name)
+         (system system)
+         (target target)
+         (host-inputs `(,@(if source
+                              `(("source" ,source))
+                              '())
+                        ,@inputs
+
+                        ;; Keep the standard inputs of 'gnu-build-system'
+                        ,@(standard-packages)))
+         (build-inputs `(("cargo" ,cargo)
+                         ("rustc" ,rustc)
+                         ,@native-inputs))
+         (outputs outputs)
+         (build (if target cargo-cross-build cargo-build))
+         (arguments (strip-keyword-arguments private-keywords arguments)))))
+
+(define cargo-build-system
+  (build-system
+    (name 'cargo)
+    (description
+     "Cargo build system, to build Rust crates")
+    (lower lower)))
diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm
new file mode 100644
index 0000000..abe7e05
--- /dev/null
+++ b/guix/build/cargo-build-system.scm
@@ -0,0 +1,81 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2016 David Craven <david@craven.ch>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix build cargo-build-system)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module (guix build utils)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 ftw)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:export (%standard-phases
+            cargo-build))
+
+;; Commentary:
+;;
+;; Builder-side code of the standard Rust package build procedure.
+;;
+;; Code:
+
+(define* (configure #:rest _)
+  "Replace Cargo.toml [dependencies] section with guix inputs."
+  ;; TODO
+  #t)
+
+(define* (build #:key (cargo-build-flags '("--release")) #:allow-other-keys)
+  "Build a given Cargo package."
+  (zero? (apply system* `("cargo" "build" ,@cargo-build-flags))))
+
+(define* (check #:rest _)
+  "Run tests for a given Cargo package."
+  (zero? (system* "cargo" "test")))
+
+(define* (install #:key inputs outputs #:allow-other-keys)
+  "Install a given Cargo package."
+  (let* ((out (assoc-ref outputs "out"))
+         (src (assoc-ref inputs "source"))
+         (bin (string-append out "/bin"))
+         (rsrc (string-append out "/rustsrc")))
+    (mkdir-p rsrc)
+    ;; Rust doesn't have a stable ABI yet. Because of this
+    ;; Cargo doesn't have a search path for binaries yet.
+    ;; Until this changes we are working around this by
+    ;; distributing crates as source and replacing
+    ;; references in Cargo.toml with store paths.
+    (copy-recursively "src" (string-append rsrc "/src"))
+    (install-file "Cargo.toml" rsrc)
+    ;; When the package includes executables we install
+    ;; it using cargo install. This fails when the crate
+    ;; doesn't contain an executable.
+    (system* "cargo" "install" "--root" bin)
+    #t))
+
+(define %standard-phases
+  ;; 'configure' phase is not needed.
+  (modify-phases gnu:%standard-phases
+    (replace 'configure configure)
+    (replace 'build build)
+    (replace 'check check)
+    (replace 'install install)))
+
+(define* (cargo-build #:key inputs (phases %standard-phases)
+                      #:allow-other-keys #:rest args)
+  "Build the given Cargo package, applying all of PHASES in order."
+  (apply gnu:gnu-build #:inputs inputs #:phases phases args))
+
+;;; cargo-build-system.scm ends here