(zilch lang rust): document
Change-Id: I6a6a6964c8aaff8d5f3e18bc5c7486746b5a2952
This commit is contained in:
parent
ae774da043
commit
0340f6e830
10 changed files with 597 additions and 82 deletions
179
docs/modules/ROOT/pages/rust/library.adoc
Normal file
179
docs/modules/ROOT/pages/rust/library.adoc
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
= Zilch's Rust support
|
||||
:page-pagination:
|
||||
|
||||
Zilch supports using the Rust compiler directly from Scheme. This can be used
|
||||
to compile single crates, or to drive the crate support more directly.
|
||||
|
||||
== Compiling Rust code
|
||||
To compile Rust code, xref:generated:zilch.lang.rust.adoc[`(zilch lang rust)`]
|
||||
provides a `call-rustc` procedure, which handles most of the code necessary to
|
||||
compile Rust code.
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
(define gcc (cdr (assoc "out" (nixpkgs "gcc")))) ; <1>
|
||||
|
||||
(define outputs
|
||||
(call-rustc
|
||||
(zfile "fn main() { println!(\"Hello, World!\"); }") ; <2>
|
||||
'() ; <3>
|
||||
crate-type: 'bin
|
||||
crate-name: "hello"
|
||||
edition: "2021"
|
||||
emits: '(dep-info: #t link: #t) ; <4>
|
||||
codegen-flags: (cons "linker" #~,(string-append #$gcc "/bin/cc")))) ; <5>
|
||||
;; (("dep-info"
|
||||
;; . #<store path /0fnqikhh1rkw991hd37lzxhk13w5zzcs770jvn629d9mrs0zb6ax
|
||||
;; (ca~ /nix/store/x1g5mjj7py2r263ma5314phfpcwa4gg7-rustc-bin-hello.drv!dep-info)>)
|
||||
;; ("link"
|
||||
;; . #<store path /05lmqcyk0kazll2i1jcp7299pfgxnxj0hw32hllxkb1iqxs4fqbm
|
||||
;; (ca~ /nix/store/x1g5mjj7py2r263ma5314phfpcwa4gg7-rustc-bin-hello.drv!link)>))
|
||||
----
|
||||
<1> Get a copy of the `out` output for `gcc` in nixpkgs.
|
||||
<2> Use a file defined inline to compile.
|
||||
<3> Don't add any variables to the environment.
|
||||
<4> Make `rustc` only emit the `dep-info` and `link` outputs.
|
||||
<5> Tell `rustc` where to find the linker.
|
||||
|
||||
The resulting binary is linked dynamically, using the linker passed either as
|
||||
`codegen-flag`, or findable as `cc` on `$PATH`. By default, each `emits:` entry
|
||||
ends up as a separate output; they are all part of the same derivation, though.
|
||||
|
||||
To compile more than one file, a more involved structure has to be used; Rust
|
||||
expects the file structure to exist on-disk, and `mod` works relative to the
|
||||
directory the current file is in.
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
|
||||
(define multi-file
|
||||
(zdir
|
||||
"lib.rs" (zfile "pub mod foo;")
|
||||
"foo.rs" (zfile "pub fn hello(who: String) { println!(\"Hello, {who}\"); }")))
|
||||
|
||||
(define outputs
|
||||
(call-rustc
|
||||
#~,(string-append #$multi-file "/lib.rs") ; <1>
|
||||
'()
|
||||
crate-type: 'rlib
|
||||
crate-name: "hello-lib"
|
||||
edition: "2021"
|
||||
emits: '(metadata: #t link: #t)))
|
||||
----
|
||||
<1> Import `multi-file` (the directory) to store, append `/lib.rs` to its store
|
||||
path, then use that as the file input to `rustc`. This construction is
|
||||
equivalent to `"${./multi-file}/lib.rs"` in Nix.
|
||||
|
||||
== Reading Cargo files
|
||||
Cargo files contain a lot of automated detection of paths. For example, a
|
||||
binary can live at `src/bin.rs`, or at `src/bins/foo.rs`. To handle this, Zilch
|
||||
requires the Cargo file be accompanied with a VFS representing the crate. To
|
||||
handle workspaces, parsing a `Cargo.toml` takes a workspace, and returns both
|
||||
the crate _and_ workspace defined in the file, where applicable. This is
|
||||
handled by xref:generated:zilch.lang.rust.cargo.adoc[`parse-cargo-toml`].
|
||||
|
||||
Reading a `Cargo.lock` is handled by `parse-lockfile`, which takes in the
|
||||
contents of a lockfile, and parses it into a list of lockfile entries. Each
|
||||
entry in the lockfile can be used in xref:generated:zilch.lang.rust.registry.adoc[`fetch-and-unpack-crate`]
|
||||
to fetch the crate, though this is usually done automatically by the resolver.
|
||||
|
||||
== The resolver
|
||||
The rust resolver in Zilch handles selecting dependencies from a `Cargo.lock`
|
||||
for one or more Cargo targets.
|
||||
|
||||
To use it, first parse a lockfile and a `Cargo.toml`:
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
(define example-vfs (vfs-from-directory "example"))
|
||||
|
||||
(define lockfile
|
||||
(parse-lockfile
|
||||
(read-file "example/Cargo.lock"))) <1>
|
||||
|
||||
(define-values (crate workspace)
|
||||
(parse-cargo-toml
|
||||
example-vfs
|
||||
(read-file "example/Cargo.toml")
|
||||
#f))
|
||||
----
|
||||
<1> `read-file` is an example procedure, and isn't part of Zilch (yet).
|
||||
|
||||
If desired, add more `Cargo.toml` files to prefer over any crates found in the
|
||||
`Cargo.lock`:
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
(define override-vfs (vfs-from-directory "override-example"))
|
||||
(define-values (override-crate workspace)
|
||||
(parse-cargo-toml
|
||||
override-vfs
|
||||
(read-file "override-example/Cargo.toml")
|
||||
#f))
|
||||
----
|
||||
|
||||
Then, call the resolver:
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
(define targets
|
||||
(process-many-with-lockfile
|
||||
(list
|
||||
(cons crate example-vfs)
|
||||
(cons override-crate override-vfs))
|
||||
lockfile))
|
||||
----
|
||||
|
||||
The result from `process-many-with-lockfile` is a list of `<resolved-package>`
|
||||
records, each representing a Cargo target in the crates that have been
|
||||
processed.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
By default, the resolver will set _no_ features on any crate, not even default
|
||||
ones. As exception, if a binary crate is available, it _will_ have the default
|
||||
feature set, which is likely to percolate down to most other crates it depends
|
||||
on. There is currently no way to change this behavior easily.
|
||||
====
|
||||
|
||||
When a target is to be built, it can be used with `build-package`:
|
||||
|
||||
[,scheme,line-comment=;]
|
||||
----
|
||||
(define (build-script-env-override-example crate-name is-dependency)
|
||||
(if (and is-dependency (string=? crate-name "test")) ; <1>
|
||||
(list
|
||||
(cons
|
||||
"PATH"
|
||||
#~,(string-append
|
||||
#$(cdr (assoc "out" (nixpkgs "hello")))
|
||||
"/bin")))
|
||||
#f))
|
||||
(define (compiler-env-override-example crate-name is-dependency)
|
||||
(if (and (not is-dependency) (string=? crate-name "test")) ; <2>
|
||||
'(("HELLO_STRING" . "Hello!"))
|
||||
#f))
|
||||
|
||||
(map
|
||||
(lambda (pkg)
|
||||
(build-package
|
||||
pkg
|
||||
build-script-env-override-example
|
||||
compiler-env-overridde-example)) ; <3>
|
||||
targets)
|
||||
----
|
||||
<1> Add GNU Hello to the PATH of any build script that depends on the crate `test`
|
||||
<2> If the crate `test` is built, set `HELLO_STRING`. This can be read from
|
||||
Rust using the `env!` macro.
|
||||
<3> Use the overrides when building each crate's targets.
|
||||
|
||||
[#limits]
|
||||
=== Limitations
|
||||
The `(zilch lang rust resolver)` implements most of the logic of a https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions[v1 resolver],
|
||||
which handles selecting dependencies from a set of candidates. The biggest
|
||||
difference between Zilch's resolver and the canonical implemention (in Cargo)
|
||||
is that the resolver in Zilch will never fall back to looking for a candidate
|
||||
inside any configured Cargo registries. If a crate isn't found in the `Cargo.lock`
|
||||
it is considered an error. While this makes using Zilch a tad more cumbersome,
|
||||
it means that Zilch itself is entirely deterministic on the files of the crates
|
||||
it processes.
|
||||
63
docs/modules/ROOT/pages/rust/man.adoc
Normal file
63
docs/modules/ROOT/pages/rust/man.adoc
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
= zilch-cli-rust(1)
|
||||
Puck Meerburg
|
||||
v0.0.1
|
||||
:doctype: manpage
|
||||
:manmanual: ZILCH-CLI-RUST
|
||||
:mansource: ZILCH
|
||||
:page-pagination: prev
|
||||
|
||||
== Name
|
||||
|
||||
zilch-cli-rust - builds a Rust crate using Zilch
|
||||
|
||||
== Synopsis
|
||||
|
||||
*zilch-cli-rust* [_OPTION_]... [_TARGET_]...
|
||||
|
||||
== Description
|
||||
|
||||
This command uses Zilch to build one or more Rust crate targets
|
||||
entirely inside Nix, using content-addressed derivations.
|
||||
|
||||
This program requires a Rust crate consisting of a `Cargo.toml` and
|
||||
with a valid and up-to-date `Cargo.lock`.
|
||||
|
||||
== Options
|
||||
|
||||
*-h*::
|
||||
*--help*::
|
||||
Print a help message
|
||||
|
||||
*-j* _COUNT_::
|
||||
*--max-jobs* _COUNT_::
|
||||
The maximum amount of builds to run. Defaults to the amount of cores.
|
||||
|
||||
*-v*::
|
||||
*--verbose*::
|
||||
Increase the verbosity configured in the Nix daemon. Can be specified
|
||||
multiple times.
|
||||
|
||||
*-L*::
|
||||
*--print-build-logs*::
|
||||
Print derivation logs as they come in.
|
||||
|
||||
*-m* _DIR_::
|
||||
*--crate-dir* _DIR_::
|
||||
The directory to use as crate. This is also the crate that will be
|
||||
used to find viable targets to build.
|
||||
|
||||
*-r* _DIR_::
|
||||
*--replace* _DIR_::
|
||||
Add the crate in _DIR_ to the possible dependencies, prioritizing it
|
||||
over any crate in the `Cargo.lock`. Can be specified multiple times.
|
||||
|
||||
*-z* _PATH_::
|
||||
*--overrides* _PATH_::
|
||||
Read build script overrides from this file. By default, a stock set
|
||||
of overrides is used, aimed at some crates. If an empty string is
|
||||
used for _PATH_, this default set of overrides is not used. Can be
|
||||
specified multiple times.
|
||||
|
||||
*--debug*::
|
||||
Crash on the first error, rather than continuing to build the next
|
||||
target.
|
||||
76
docs/modules/ROOT/pages/rust/usage.adoc
Normal file
76
docs/modules/ROOT/pages/rust/usage.adoc
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
= Usage
|
||||
:page-pagination: next
|
||||
|
||||
Using `zilch-cli-rust`, you can compile Rust code incrementally crate-by-crate:
|
||||
|
||||
[,console]
|
||||
----
|
||||
$ git clone https://github.com/BurntSushi/ripgrep
|
||||
|
||||
$ zilch-cli-rust --crate-dir ripgrep/ <1>
|
||||
…
|
||||
ripgrep rg bin /nix/store/pxvqbn65lv2f4r3x1cszv5pr5l5mjwh9-rustc-bin-rg-link
|
||||
grep grep lib not a binary
|
||||
grep-cli grep_cli lib not a binary
|
||||
globset globset lib not a binary
|
||||
…
|
||||
|
||||
$ zilch-cli-rust --crate-dir ripgrep/ rg <2>
|
||||
…
|
||||
ripgrep rg bin /nix/store/pxvqbn65lv2f4r3x1cszv5pr5l5mjwh9-rustc-bin-rg-link
|
||||
----
|
||||
<1> Building all binary targets in a crate
|
||||
<2> Building a specifically targeted target in a crate
|
||||
|
||||
Right now, the daemon has to be able to build `x86_64-linux` derivations, and
|
||||
only the `x86_64-unknown-linux-gnu` Rust target is supported.
|
||||
|
||||
== Replacing dependencies
|
||||
As part of Zilch, it's also possible to quickly build a crate with one of its
|
||||
(transitive) dependencies replaced:
|
||||
|
||||
[,console]
|
||||
----
|
||||
$ git clone https://github.com/BurntSushi/bstr
|
||||
$ zilch-cli-rust --crate-dir ripgrep/ --replace bstr/ rg
|
||||
…
|
||||
ripgrep rg bin /nix/store/6rf4r69sp8ijg2xrdglbnx8smphfg6fr-rustc-bin-rg-link
|
||||
----
|
||||
|
||||
After editing a file any of the replaced dependencies, if this crate is used in
|
||||
the final build, Zilch will apply early-cutoff where possible.
|
||||
|
||||
[IMPORTANT]
|
||||
.Limitations in dependency selection
|
||||
====
|
||||
Zilch will never select dependencies that aren't found in the lockfile of the
|
||||
primary crate. If there is no lockfile, or the dependencies have changed, it is
|
||||
necessary to manually run `cargo generate-lockfile` to make sure all
|
||||
dependencies can be found. See xref:./library.adoc#limits[The resolver's limitations] for more info.
|
||||
====
|
||||
|
||||
== Build script overrides
|
||||
Many Rust projects use build scripts that require external dependencies. To add
|
||||
support for these, you can use a JSON file.
|
||||
|
||||
This file consists of a JSON object, where the key is the name of a crate, and
|
||||
the value is an object with the following keys:
|
||||
|
||||
- `buildScript`: Overrides to apply when executing the build script for this
|
||||
crate
|
||||
- `buildScriptDependency`: Overrides to apply when executing the build script
|
||||
for any crate that depends on this crate.
|
||||
- `rustc`: Environment variables to add to the `rustc` invocation for this
|
||||
crate.
|
||||
|
||||
Each of these keys contains a map of key to a Nix expression evaluated inside
|
||||
`nixpkgs`. If multiple overrides apply to a single crate, each value is
|
||||
concatenated, separated with a `:`. Zilch's runner supports `_zilch_pkgconfig`
|
||||
as an environment variable as special case to add both `lib/pkgconfig` and
|
||||
`share/pkgconfig`, as well as the paths inside all the `propagated-build-inputs`
|
||||
of each store path, making `pkg-config` work. See
|
||||
https://puck.moe/git/zilch/tree/cli/overrides.json[the default overrides] for
|
||||
more examples on real-life crates.
|
||||
|
||||
If this mechanism is not expressive enough, it's possible to use Zilch's Rust
|
||||
support directly, as a Scheme library.
|
||||
Loading…
Add table
Add a link
Reference in a new issue