├── .github └── workflows │ ├── binary-cache.yaml │ ├── ci.yaml │ └── update.yaml ├── .gitignore ├── LICENSE-MIT ├── README.md ├── build-rust-crate ├── builder-bin.sh ├── builder-build-script.sh ├── builder-common.sh ├── builder-lib.sh └── default.nix ├── cache ├── Cargo.lock ├── Cargo.toml ├── default.nix └── src │ └── lib.rs ├── crates-io-override ├── default.nix └── proc-macro.nix ├── flake.lock ├── flake.nix ├── lib ├── default.nix ├── glob.nix ├── pkg-info.nix ├── resolve.nix ├── semver.nix ├── support.nix └── target-cfg.nix ├── noc ├── Cargo.lock ├── Cargo.toml ├── src │ ├── init.rs │ └── main.rs └── templates │ └── init-flake.nix ├── scripts └── cratesio-utils.py ├── tests ├── build-deps │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── main.rs ├── build-feature-env-vars │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── main.rs ├── cap-lints │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── crate-names │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── custom-lib-name │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── default.nix ├── dependency-v1 │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── dependency-v2 │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── dependency-v3 │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── fake-semver │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── features │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── libz-dynamic │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── main.rs ├── libz-static │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── lto-fat │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── lto-proc-macro │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── lto-thin │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── tokio-app │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── workspace-inline │ ├── Cargo.lock │ ├── Cargo.toml │ ├── bar │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── baz │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── src │ │ └── main.rs ├── workspace-proc-macro-lto │ ├── Cargo.lock │ ├── Cargo.toml │ ├── procm │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── src │ │ └── lib.rs └── workspace-virtual │ ├── Cargo.lock │ ├── Cargo.toml │ └── crates │ ├── bar │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── exc │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── foo │ ├── Cargo.toml │ └── src │ └── main.rs └── toml2json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── default.nix └── src └── main.rs /.github/workflows/binary-cache.yaml: -------------------------------------------------------------------------------- 1 | name: Binary cache 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | workflow_run: 8 | workflows: 9 | - update 10 | types: 11 | - completed 12 | branches: 13 | - main 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | populate: 20 | name: Build and push binary cache 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - name: Install Nix 26 | uses: cachix/install-nix-action@v24 27 | - name: Setup Cachix 28 | uses: cachix/cachix-action@v13 29 | with: 30 | name: nocargo 31 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 32 | pushFilter: '(-source$|\.tar\.gz)' 33 | 34 | - name: Build cached crates 35 | run: nix build .#cache --show-trace --no-update-lock-file 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | 6 | workflow_run: 7 | workflows: 8 | - update 9 | types: 10 | - completed 11 | branches: 12 | - main 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | flake-check: 19 | name: Flake check (locked) 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Install Nix 25 | uses: cachix/install-nix-action@v24 26 | - run: nix flake check --show-trace --no-update-lock-file 27 | -------------------------------------------------------------------------------- /.github/workflows/update.yaml: -------------------------------------------------------------------------------- 1 | name: Update registry 2 | on: 3 | schedule: 4 | - cron: '0 3 * * *' # *-*-* 03:00:00 UTC 5 | 6 | workflow_dispatch: 7 | 8 | env: 9 | BRANCH: main 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | update: 16 | name: Update registry 17 | runs-on: ubuntu-latest 18 | env: 19 | CRATES_TOML_DIR: ./crates-toml 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | rev: ${{ env.BRANCH }} 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | - name: Install Nix 27 | uses: cachix/install-nix-action@v24 28 | with: 29 | nix_path: nixpkgs=channel:nixpkgs-unstable 30 | 31 | - name: Cache fetched Cargo.toml files 32 | uses: actions/cache@v3 33 | with: 34 | path: ${{ env.CRATES_TOML_DIR }} 35 | key: crates-toml 36 | 37 | - name: Sync crates.io database 38 | run: ./scripts/cratesio-utils.py sync 39 | - name: Update proc-macro crates 40 | run: ./scripts/cratesio-utils.py update-proc-macro-crates 41 | - name: Update lockfile of popular crates 42 | run: ./scripts/cratesio-utils.py update-popular-crates 43 | 44 | # This should be the last. So the registry locked is not earlier than references. 45 | - name: Update flake 46 | run: nix flake update 47 | 48 | - name: Flake check 49 | run: nix flake check --show-trace --no-update-lock-file 50 | 51 | - name: Push changes 52 | run: | 53 | git config user.name github-actions 54 | git config user.email github-actions@github.com 55 | git add flake.lock crates-io-override/proc-macro.nix cache/Cargo.{lock,toml} 56 | git commit -m "registry: update" 57 | git push HEAD:${{ env.BRANCH }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | result-* 3 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 5 | of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nocargo: Cargo in Nix 2 | 3 | 🚧 *This project is under development and is not ready for production yet. APIs are subjects to change.* 4 | 5 | Build Rust crates with *Nix Build System*. 6 | - **No IFDs** (import-from-derivation). See [meme](https://gist.github.com/oxalica/d3b1251eb29d10e6f3cb2005167ddcd9). 7 | - No `cargo` dependency during building. Only `rustc`. 8 | - No need for hash prefetching or code generation[^no-code-gen]. 9 | - Crate level caching, globally shared. 10 | - [nixpkgs] integration for non-Rust dependencies. 11 | 12 | [^no-code-gen]: Initial template generation and `Cargo.lock` updatin don't count for "code generation". The former is optional, and the latter is indeed not "code". 13 | 14 |
15 | Feature checklist 16 | 17 | - Binary cache 18 | - [x] Top 256 popular crate versions with default features 19 | - Nix library 20 | - [ ] Non-flake support. 21 | - [x] `[workspace]` 22 | - [x] `members` 23 | - [ ] Auto-`members` 24 | - [x] `excludes` 25 | FIXME: Buggy. 26 | - [ ] `resolver` 27 | Currently has custom resolution algorithm, more like v2. 28 | - [x] `links` 29 | - [x] `[profile]` 30 | - [x] `[{,dev-,build-}dependencies]` 31 | - [x] `[features]` 32 | - [x] Overriding API 33 | - [x] `[target..dependencies]` 34 | - [x] `[patch]` 35 | Automatically supported. Since the dependency graph `Cargo.lock` currently relies on `cargo`'s generation. 36 | - [ ] Cross-compilation. 37 | FIXME: Buggy with proc-macros. 38 | - `noc` helper 39 | - [x] `noc init`: Initial template `flake.nix` generation 40 | - Dependency kinds 41 | - [ ] `registry` 42 | - [x] `registry-index` 43 | - [x] `git` 44 | - [x] `path` inside workspace 45 | - [ ] `path` outside workspace 46 | - Target detection 47 | - [ ] Library 48 | FIXME: Assume to always exist. 49 | - [x] Binary 50 | - [ ] Test 51 | - [ ] Bench 52 | - [ ] Example 53 | - [ ] `Cargo.lock` generation and updating 54 | 55 |
56 | 57 | ## Start with Nix flake 58 | 59 | 1. (Optional) Add binary substituters for pre-built popular crates, by either 60 | - Install `cachix` and run `cachix use nocargo` ([see more detail about `cachix`](https://app.cachix.org/cache/nocargo)), or 61 | - Manually add substituter `https://nocargo.cachix.org` with public key `nocargo.cachix.org-1:W6jkp5htZBA1tUdU8XHLaD7zBrIFnor0MsLhHgrJeHk=` 62 | 1. Enter the root directory of your rust workspace or package. Currently, you should have `Cargo.lock` already created by `cargo`. 63 | 1. Run `nix run github:oxalica/nocargo init` to generate `flake.nix`. Or write it by hand by following [the next section](#example-flake.nix-structure). 64 | 1. Check flake outputs with `nix flake show`. Typically, the layout would be like, 65 | ``` 66 | └───packages 67 | └───x86_64-linux 68 | ├───default: package 'rust_mypkg1-0.1.0' # The "default" package. For workspace, it's the top-level one if exists. 69 | ├───mypkg1: package 'rust_mypkg1-0.1.0' # Crate `mypkg1` with `release` profile. 70 | ├───mypkg1-dev: package 'rust_mypkg1-debug-0.1.0' # Crate `mypkg1` with `dev` profile. 71 | ├───mypkg2: package 'rust_mypkg2-0.1.0' # etc. 72 | └───mypkg2-dev: package 'rust_mypkg2-debug-0.1.0' 73 | ``` 74 | 1. Run `nix build .#` to build your package. Built binaries (if any) will be placed in `./result/bin`, and the library will be in `./result/lib`. 75 | 1. Have fun! 76 | 77 | ## Example `flake.nix` structure for reference 78 | 79 | A template `flake.nix` with common setup are below. It's mostly the same as the generated one, except that the helper `noc` will scan the workspace and discover all external registries and git dependencies for you. 80 | 81 | ```nix 82 | { 83 | description = "My Rust packages"; 84 | 85 | inputs = { 86 | nixpkgs.url = "github:NixOS/nixpkgs"; 87 | flake-utils.url = "github:numtide/flake-utils"; 88 | nocargo = { 89 | url = "github:oxalica/nocargo"; 90 | inputs.nixpkgs.follows = "nixpkgs"; 91 | 92 | # See below. 93 | # inputs.registry-crates-io.follows = "registry-crates-io"; 94 | }; 95 | 96 | # Optionally, you can explicitly import crates.io-index here. 97 | # So you can `nix flake update` at any time to get cutting edge version of crates, 98 | # instead of waiting `nocargo` to dump its dependency. 99 | # Otherwise, you can simply omit this to use the locked registry from `nocargo`, 100 | # which is updated periodically. 101 | # registry-crates-io = { url = "github:rust-lang/crates.io-index"; flake = false; }; 102 | }; 103 | 104 | outputs = { nixpkgs, flake-utils, nocargo, ... }@inputs: 105 | flake-utils.lib.eachSystem [ "x86_64-linux" ] (system: 106 | let 107 | # The entry API to make Nix derivations from your Rust workspace or package. 108 | # The output of it consists of profile names, like `release` or `dev`, each of which is 109 | # a attrset of all member package derivations keyed by their package names. 110 | ws = nocargo.lib.${system}.mkRustPackageOrWorkspace { 111 | # The root directory, which contains `Cargo.lock` and top-level `Cargo.toml` 112 | # (the one containing `[workspace]` for workspace). 113 | src = ./.; 114 | 115 | # If you use registries other than crates.io, they should be imported in flake inputs, 116 | # and specified here. Note that registry should be initialized via `mkIndex`, 117 | # with an optional override. 118 | extraRegistries = { 119 | # "https://example-registry.org" = nocargo.lib.${system}.mkIndex inputs.example-registry {}; 120 | }; 121 | 122 | # If you use crates from git URLs, they should be imported in flake inputs, 123 | # and specified here. 124 | gitSrcs = { 125 | # "https://github.com/some/repo" = inputs.example-git-source; 126 | }; 127 | 128 | # If some crates in your dependency closure require packages from nixpkgs. 129 | # You can override the argument for `stdenv.mkDerivation` to add them. 130 | # 131 | # Popular `-sys` crates overrides are maintained in `./crates-io-override/default.nix` 132 | # to make them work out-of-box. PRs are welcome. 133 | buildCrateOverrides = with nixpkgs.legacyPackages.${system}; { 134 | # Use package id format `pkgname version (registry)` to reference a direct or transitive dependency. 135 | "zstd-sys 2.0.1+zstd.1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = old: { 136 | nativeBuildInputs = [ pkg-config ]; 137 | propagatedBuildInputs = [ zstd ]; 138 | }; 139 | 140 | # Use package name to reference local crates. 141 | "mypkg1" = old: { 142 | nativeBuildInputs = [ git ]; 143 | }; 144 | }; 145 | 146 | # We use the rustc from nixpkgs by default. 147 | # But you can override it, for example, with a nightly version from https://github.com/oxalica/rust-overlay 148 | # rustc = rust-overlay.packages.${system}.rust-nightly_2022-07-01; 149 | }; 150 | in rec { 151 | # For convenience, we hoist derivations of `release` and `dev` profile for easy access, 152 | # with `dev` packages postfixed by `-dev`. 153 | # You can export different packages of your choice. 154 | packages = ws.release 155 | // nixpkgs.lib.mapAttrs' (name: value: { name = "${name}-dev"; inherit value; }) ws.dev 156 | // { 157 | # The "default" features are turned on by default. 158 | # You can `override` the library derivation to enable a different set of features. 159 | # Explicit overriding will disable "default", unless you manually include it. 160 | mypkg1-with-custom-features = (ws.release.mypkg1.override { 161 | # Enables two features (and transitive ones), and disables "default". 162 | features = [ "feature1" "feature2" ]; 163 | }).bin; 164 | }; 165 | }); 166 | } 167 | ``` 168 | 169 | ## FAQ 170 | 171 | ### Comparison with [cargo2nix] and [naersk]? 172 | 173 | Main differences are already clarified [on the top](#nocargo%3A-cargo-in-nix). 174 | 175 | `nocargo` is inspired by `cargo2nix` and `buildRustCrate` in `nixpkgs`. We are more like `cargo2nix` while the generation part is implemented by pure Nix, but less like `naersk` which is a wrapper to call `cargo` to build the package inside derivations. 176 | 177 | In other words, we and `cargo2nix` use Nix as a *Build System*, while `nearsk` use Nix as a *Package Manager* or *Packager*. 178 | 179 |
180 | 181 | Detail comparison of nocargo, cargo2nix/buildRustCrate, naersk and buildRustPackage 182 | 183 | 184 | 185 | | | nocargo | [cargo2nix]/`buildRustCrate` | [naersk] | `buildRustPackage` | 186 | |-|-|-|-|-| 187 | | Depend on `cargo` | Updating `Cargo.lock` | Updating & generating & building | Updating & vendoring & building | Building | 188 | | Derivation granularity | Per crate | Per crate | Per package + one dependency closure | All in one | 189 | | Crate level sharing | ✔️ | ✔️ | ✖ | ✖ | 190 | | Binary substitution per crate | ✔️ | Not implemented | ✖ | ✖ | 191 | | Code generation | ✖ | ✔️ | ✖ | ✖ | 192 | | Edit workspace & rebuild | Rebuild leaf crates | Rebuild leaf crates | Rebuild leaf crates | Refetch and rebuild all crates | 193 | | Edit dependencies & rebuild | Rebuild changed crates (refetch if needed) | Refetch, regenerate and rebuild changed crates | Refetch and rebuild all crates | Refetch and rebuild all crates | 194 | | Offline rebuild as long as | Not adding unfetched crate dependency | Not adding unfetched crate dependency | Not changing any dependencies | ✖ | 195 | 196 |
197 | 198 | ### But why pure Nix build system? 199 | 200 | - Sharing through fine-grained derivations between all projects, not just in one workspace. 201 | - Binary substitution per crate. 202 | No need for global `target_dir`/`CARGO_TARGET_DIR` or [sccache]. 203 | - Easy `nixpkgs` integration for non-Rust package dependencies, cross-compilation (planned) and package overriding. 204 | - More customizability: per-crate `rustc` flags tweaking, arbitrary crate patching, force dynamic linking and more. 205 | 206 | ### Can I really throw away `cargo`? 207 | 208 | Sorry, currently no. :crying_cat_face: Updating of `Cargo.lock` still relies on `cargo`. 209 | This can happen when creating a new project or changing dependencies. 210 | We are mainly using `cargo`'s SAT solver to pin down the dependency graph. 211 | 212 | It's *possible* to implement it ourselves, but not yet, due to the complexity. 213 | 214 | ## License 215 | 216 | MIT Licensed. 217 | 218 | [nixpkgs]: https://github.com/NixOS/nixpkgs 219 | [naersk]: https://github.com/nix-community/naersk 220 | [cargo2nix]: https://github.com/cargo2nix/cargo2nix 221 | [sccache]: https://github.com/mozilla/sccache 222 | -------------------------------------------------------------------------------- /build-rust-crate/builder-bin.sh: -------------------------------------------------------------------------------- 1 | source $stdenv/setup 2 | source $builderCommon 3 | 4 | declare -A buildFlagsMap 5 | declare -A binPathMap 6 | 7 | dontInstall=1 8 | 9 | addBin() { 10 | local name="$1" path="$2" binEdition="$3" 11 | local -a pathCandidates 12 | 13 | if [[ -z "$name" ]]; then 14 | echo "Name of the binary target is not specified" 15 | exit 1 16 | fi 17 | 18 | if [[ -n "$path" ]]; then 19 | pathCandidates=("$path") 20 | else 21 | pathCandidates=() 22 | if [[ -f "src/bin/$name.rs" ]]; then 23 | pathCandidates+=("src/bin/$name.rs") 24 | fi 25 | if [[ -f "src/bin/$name/main.rs" ]]; then 26 | pathCandidates+=("src/bin/$name/main.rs") 27 | fi 28 | if [[ "$name" == "$pkgName" && -f "src/main.rs" ]]; then 29 | pathCandidates+=("src/main.rs") 30 | fi 31 | fi 32 | 33 | case ${#pathCandidates[@]} in 34 | 0) 35 | echo "Cannot guess path of binary target" 36 | exit 1 37 | ;; 38 | 1) 39 | path="${pathCandidates[0]}" 40 | ;; 41 | *) 42 | echo "Ambiguous binary target, candidate paths: ${pathCandidates[*]}" 43 | exit 1 44 | ;; 45 | esac 46 | 47 | printf "Found binary %q at %q\n" "$name" "$path" 48 | binPathMap["$path"]=1 49 | 50 | # TODO: Other flags. 51 | buildFlagsMap["$name"]="$path --crate-name ${name//-/_} -C metadata=$rustcMeta-$name" 52 | if [[ -n "${binEdition:=$globalEdition}" ]]; then 53 | buildFlagsMap["$name"]+=" --edition $binEdition" 54 | fi 55 | } 56 | 57 | configurePhase() { 58 | runHook preConfigure 59 | 60 | convertCargoToml 61 | 62 | globalEdition="$(jq --raw-output '.package.edition // ""' "$cargoTomlJson")" 63 | pkgName="$(jq --raw-output '.package.name // ""' "$cargoTomlJson")" 64 | 65 | # For packages with the 2015 edition, the default for auto-discovery is false if at least one target is 66 | # manually defined in Cargo.toml. Beginning with the 2018 edition, the default is always true. 67 | # See: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#target-auto-discovery 68 | autoDiscovery= 69 | if [[ 70 | "$(jq --raw-output '.package.autobins' "$cargoTomlJson")" != false && 71 | ( "${globalEdition:-2015}" != 2015 || ${#buildFlagsMap[@]} = 0 ) 72 | ]]; then 73 | autoDiscovery=1 74 | fi 75 | 76 | local binsStr 77 | while read -r name; do 78 | read -r path 79 | read -r binEdition 80 | addBin "$name" "$path" "$binEdition" 81 | # Don't strip whitespace. 82 | done < <(jq --raw-output '.bin // [] | .[] | .name // "", .path // "", .edition // ""' "$cargoTomlJson") 83 | 84 | if [[ -n "$autoDiscovery" ]]; then 85 | if [[ -f src/main.rs && -z ${binPathMap[src/main.rs]} ]]; then 86 | addBin "$pkgName" src/main.rs "" 87 | fi 88 | local f name 89 | for f in src/bin/*; do 90 | name="${f##*/}" 91 | if [[ "$f" = *.rs && -f "$f" ]]; then 92 | [[ -n "${binPathMap["$f"]}" ]] || addBin "${name%.rs}" "$f" "" 93 | elif [[ -f "$f/main.rs" ]]; then 94 | [[ -n "${binPathMap["$f/main.rs"]}" ]] || addBin "$name" "$f/main.rs" "" 95 | fi 96 | done 97 | fi 98 | 99 | if [[ ${#buildFlagsMap[@]} = 0 ]]; then 100 | echo "No binaries to be built" 101 | mkdir $out 102 | exit 0 103 | fi 104 | 105 | # Implicitly link library of current crate, if exists. 106 | if [[ -e "$libDevDrv"/lib ]]; then 107 | addExternFlags buildFlagsArray link "::$libOutDrv:$libDevDrv" 108 | fi 109 | 110 | # Actually unused. 111 | declare -a cdylibBuildFlagsArray 112 | 113 | addExternFlags buildFlagsArray link $dependencies 114 | addFeatures buildFlagsArray $features 115 | importBuildOut buildFlagsArray cdylibBuildFlagsArray "$buildDrv" 116 | setCargoCommonBuildEnv 117 | 118 | depsClosure="$(mktemp -d)" 119 | collectTransDeps "$depsClosure" $dependencies 120 | buildFlagsArray+=(-Ldependency="$depsClosure") 121 | 122 | runHook postConfigure 123 | } 124 | 125 | buildPhase() { 126 | runHook preBuild 127 | 128 | mkdir -p $out/bin 129 | 130 | local binName 131 | for binName in "${!buildFlagsMap[@]}"; do 132 | export CARGO_CRATE_NAME="$binName" 133 | export CARGO_BIN_NAME="$binName" 134 | runRustc "Building binary $binName" \ 135 | ${buildFlagsMap["$binName"]} \ 136 | --crate-type bin \ 137 | --out-dir $out/bin \ 138 | $buildFlags \ 139 | "${buildFlagsArray[@]}" 140 | done 141 | 142 | runHook postBuild 143 | } 144 | 145 | genericBuild 146 | -------------------------------------------------------------------------------- /build-rust-crate/builder-build-script.sh: -------------------------------------------------------------------------------- 1 | source $stdenv/setup 2 | source $builderCommon 3 | shopt -s nullglob 4 | 5 | preInstallPhases+="runBuildScriptPhase " 6 | 7 | buildFlagsArray+=( -Cmetadata="$rustcMeta" ) 8 | 9 | configurePhase() { 10 | runHook preConfigure 11 | 12 | convertCargoToml 13 | 14 | buildScriptSrc="$(jq --raw-output '.package.build // ""' "$cargoTomlJson")" 15 | if [[ -z "$buildScriptSrc" && -e build.rs ]]; then 16 | buildScriptSrc=build.rs 17 | elif [[ -z "$buildScriptSrc" ]]; then 18 | echo "No build script, doing nothing" 19 | mkdir -p $out 20 | exit 0 21 | fi 22 | 23 | edition="$(jq --raw-output '.package.edition // ""' "$cargoTomlJson")" 24 | if [[ -n "$edition" ]]; then 25 | buildFlagsArray+=(--edition="$edition") 26 | fi 27 | 28 | addFeatures buildFlagsArray $features 29 | addExternFlags buildFlagsArray link $dependencies 30 | setCargoCommonBuildEnv 31 | 32 | depsClosure="$(mktemp -d)" 33 | collectTransDeps "$depsClosure" $dependencies 34 | buildFlagsArray+=(-Ldependency="$depsClosure") 35 | 36 | runHook postConfigure 37 | } 38 | 39 | buildPhase() { 40 | runHook preBuild 41 | 42 | mkdir -p $out/bin 43 | 44 | runRustc "Building build script" \ 45 | "$buildScriptSrc" \ 46 | --out-dir="$out/bin" \ 47 | --crate-name="build_script_build" \ 48 | --crate-type=bin \ 49 | --emit=link \ 50 | -Cembed-bitcode=no \ 51 | $buildScriptBuildFlags \ 52 | "${buildFlagsArray[@]}" 53 | 54 | runHook postBuild 55 | } 56 | 57 | runBuildScriptPhase() { 58 | runHook preRunBuildScript 59 | 60 | export CARGO_MANIFEST_DIR="$(pwd)" 61 | if [[ -n "$links" ]]; then 62 | export CARGO_MANIFEST_LINKS="$links" 63 | fi 64 | 65 | for feat in $features; do 66 | feat_uppercase="${feat^^}" 67 | export "CARGO_FEATURE_${feat_uppercase//-/_}"=1 68 | done 69 | 70 | export OUT_DIR="$out/rust-support/out-dir" 71 | export NUM_JOBS=$NIX_BUILD_CORES 72 | export RUSTC_BACKTRACE=1 # Make debugging easier. 73 | 74 | local buildOut 75 | for buildOut in $linksDependencies; do 76 | if [[ -e "$buildOut/rust-support/links-metadata" ]]; then 77 | source "$buildOut/rust-support/links-metadata" 78 | fi 79 | done 80 | 81 | # Other flags are set outside. 82 | # - CARGO_CFG_ 83 | # - HOST 84 | # - TARGET 85 | # - RUSTC 86 | # - CARGO 87 | # - RUSTDOC 88 | 89 | mkdir -p "$out/rust-support" "$OUT_DIR" 90 | 91 | echo "Running build script" 92 | stdoutFile="$out/rust-support/build-stdout" 93 | "$out/bin/build_script_build" | tee "$stdoutFile" 94 | 95 | runHook postRunBuildScript 96 | } 97 | 98 | installPhase() { 99 | runHook preInstall 100 | 101 | # https://doc.rust-lang.org/1.61.0/cargo/reference/build-scripts.html#outputs-of-the-build-script 102 | local line rhs 103 | while read -r line; do 104 | rhs="${line#*=}" 105 | case "$line" in 106 | cargo:rerun-if-changed=*|cargo:rerun-if-env-changed=*) 107 | # Ignored due to the sandbox. 108 | ;; 109 | cargo:rustc-link-arg=*) 110 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-link-args" 111 | ;; 112 | cargo:rustc-link-arg-bin=*) 113 | if [[ "$rhs" != *=* ]]; then 114 | echo "Missing binary name: $line" 115 | exit 1 116 | fi 117 | echo "-Clink-arg=${rhs%%=*}" >>"$out/rust-support/rustc-link-args-bin-${rhs#*=}" 118 | ;; 119 | cargo:rustc-link-arg-bins=*) 120 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-link-args-bins" 121 | ;; 122 | cargo:rustc-link-arg-tests=*) 123 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-link-args-tests" 124 | ;; 125 | cargo:rustc-link-arg-examples=*) 126 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-link-args-examples" 127 | ;; 128 | cargo:rustc-link-arg-benches=*) 129 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-link-args-benches" 130 | ;; 131 | cargo:rustc-link-lib=*) 132 | if [[ -z "$rhs" ]]; then 133 | echo "Empty link path: $line" 134 | exit 1 135 | fi 136 | echo "-l$rhs" >>"$out/rust-support/rustc-flags" 137 | ;; 138 | cargo:rustc-link-search=*) 139 | if [[ -z "$rhs" ]]; then 140 | echo "Empty link path: $line" 141 | exit 1 142 | fi 143 | echo "-L$rhs" >>"$out/rust-support/rustc-flags" 144 | ;; 145 | cargo:rustc-flags=*) 146 | local flags i flag 147 | read -r -a flags <<<"$rhs" 148 | for (( i = 0; i < ${#flags[@]}; i++ )); do 149 | flag="${flags[i]}" 150 | if [[ "$flag" = -[lL] ]]; then 151 | (( i++ )) 152 | flag+="${flags[i]}" 153 | elif [[ "$flag" != -[lL]* ]]; then 154 | echo "Only -l and -L are allowed from build script: $line" 155 | exit 1 156 | fi 157 | if [[ ${#flag} == 2 ]]; then 158 | echo "Empty link path: $line" 159 | exit 1 160 | fi 161 | echo "$flag" >>"$out/rust-support/rustc-flags" 162 | done 163 | ;; 164 | cargo:rustc-cfg=*) 165 | echo "--cfg=$rhs" >>"$out/rust-support/rustc-flags" 166 | ;; 167 | cargo:rustc-env=*=*) 168 | printf 'export %q=%q\n' "${rhs%%=*}" "${rhs#*=}" >>"$out/rust-support/rustc-env" 169 | ;; 170 | cargo:rustc-cdylib-link-arg=*) 171 | echo "-Clink-arg=$rhs" >>"$out/rust-support/rustc-cdylib-flags" 172 | ;; 173 | cargo:warning=*) 174 | printf "\033[0;1;33mWarning\033[0m: %s\n" "$rhs" 175 | ;; 176 | cargo:*=*) 177 | if [[ -n "${links:-}" ]]; then 178 | rhs="${line#*:}" 179 | local k="DEP_${links}_${rhs%%=*}" v="${rhs#*=}" 180 | k="${k^^}" 181 | k="${k//-/_}" 182 | printf 'export %q=%q\n' "$k" "$v" >>"$out/rust-support/links-metadata" 183 | else 184 | printf "\033[0;1;33mWarning\033[0m: no 'links' defined in Cargo.toml, ignoring %s" "$line" 185 | fi 186 | ;; 187 | cargo:*) 188 | echo "Unknown or wrong output line: $line" 189 | exit 1 190 | ;; 191 | *) 192 | ;; 193 | esac 194 | done <"$stdoutFile" 195 | 196 | local file 197 | for file in "$out"/rust-support/{rustc-*,links-metadata*}; do 198 | sort "$file" -o "$file" 199 | done 200 | 201 | runHook postInstall 202 | } 203 | 204 | genericBuild 205 | -------------------------------------------------------------------------------- /build-rust-crate/builder-common.sh: -------------------------------------------------------------------------------- 1 | declare -a buildFlagsArray 2 | buildFlagsArray+=( --color=always ) 3 | 4 | # Collect all transitive dependencies (symlinks). 5 | collectTransDeps() { 6 | local collectDir="$1" line rename procMacro depOut depDev 7 | mkdir -p "$collectDir" 8 | shift 9 | for line in "$@"; do 10 | IFS=: read -r rename procMacro depOut depDev <<<"$line" 11 | # May be empty. 12 | cp --no-dereference --no-clobber -t $collectDir $depDev/rust-support/deps-closure/* 2>/dev/null || true 13 | done 14 | } 15 | 16 | addExternFlags() { 17 | local var="$1" kind="$2" line rename procMacro depOut depDev paths 18 | shift 2 19 | for line in "$@"; do 20 | IFS=: read -r rename procMacro depOut depDev <<<"$line" 21 | 22 | if [[ -n "$procMacro" ]]; then 23 | paths=("$depOut"/lib/*"$sharedLibraryExt") 24 | elif [[ "$kind" == meta ]]; then 25 | paths=("$depDev"/lib/*.rmeta) 26 | else 27 | # FIXME: Currently we only link rlib. 28 | paths=("$depOut"/lib/*.rlib) 29 | fi 30 | 31 | if (( ${#paths[@]} == 0 )); then 32 | echo "No dependent library found for $line" 33 | exit 1 34 | elif (( ${#paths[@]} > 1 )); then 35 | echo "Multiple candidate found for dependent library $line, found: ${paths[*]}" 36 | exit 1 37 | fi 38 | if [[ -z "$rename" ]]; then 39 | if [[ "${paths[0]##*/}" =~ ^lib(.*)(-.*)(\.rmeta|\.rlib|"$sharedLibraryExt")$ ]]; then 40 | rename="${BASH_REMATCH[1]}" 41 | else 42 | echo "Invalid library name: ${paths[0]}" 43 | exit 1 44 | fi 45 | fi 46 | eval "$var"'+=(--extern="$rename=${paths[0]}")' 47 | done 48 | } 49 | 50 | addFeatures() { 51 | local var="$1" feat 52 | shift 53 | for feat in "$@"; do 54 | eval "$var"'+=(--cfg="feature=\"$feat\"")' 55 | done 56 | } 57 | 58 | importBuildOut() { 59 | local var="$1" cvar="$2" drv="$3" flags 60 | [[ ! -e "$drv/rust-support/build-stdout" ]] && return 61 | 62 | echo export OUT_DIR="$drv/rust-support/out-dir" 63 | export OUT_DIR="$drv/rust-support/out-dir" 64 | 65 | if [[ -e "$drv/rust-support/rustc-env" ]]; then 66 | cat "$drv/rust-support/rustc-env" 67 | source "$drv/rust-support/rustc-env" 68 | fi 69 | 70 | if [[ -e "$drv/rust-support/rustc-flags" ]]; then 71 | mapfile -t flags <"$drv/rust-support/rustc-flags" 72 | eval "$var"'+=("${flags[@]}")' 73 | fi 74 | 75 | if [[ -e "$drv/rust-support/rustc-cdylib-flags" ]]; then 76 | mapfile -t flags <"$drv/rust-support/rustc-cdylib-flags" 77 | eval "$cvar"'+=("${flags[@]}")' 78 | fi 79 | } 80 | 81 | runRustc() { 82 | local msg="$1" 83 | shift 84 | echo "$msg: RUSTC ${*@Q}" 85 | $RUSTC "$@" 86 | } 87 | 88 | convertCargoToml() { 89 | local cargoToml="${1:-"$(pwd)/Cargo.toml"}" 90 | cargoTomlJson="$(mktemp "$(dirname "$cargoToml")/Cargo.json.XXX")" 91 | toml2json <"$cargoToml" >"$cargoTomlJson" 92 | } 93 | 94 | # https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates 95 | setCargoCommonBuildEnv() { 96 | # CARGO_CRATE_NAME is set outside since targets have individual crate names. 97 | 98 | # export CARGO= 99 | CARGO_MANIFEST_DIR="$(dirname "$cargoTomlJson")" 100 | export CARGO_MANIFEST_DIR 101 | 102 | CARGO_PKG_NAME="$(jq --raw-output '.package.name // ""' "$cargoTomlJson")" 103 | CARGO_PKG_VERSION="$(jq --raw-output '.package.version // ""' "$cargoTomlJson")" 104 | if [[ -z "CARGO_PKG_NAME" ]]; then 105 | echo "Package name must be set" 106 | exit 1 107 | fi 108 | if [[ -z "CARGO_PKG_VERSION" ]]; then 109 | echo "Package version must be set" 110 | exit 1 111 | fi 112 | 113 | CARGO_PKG_AUTHORS="$(jq --raw-output '.package.authors // [] | join(":")' "$cargoTomlJson")" 114 | CARGO_PKG_DESCRIPTION="$(jq --raw-output '.package.description // ""' "$cargoTomlJson")" 115 | CARGO_PKG_HOMEPAGE="$(jq --raw-output '.package.homepage // ""' "$cargoTomlJson")" 116 | CARGO_PKG_LICENSE="$(jq --raw-output '.package.license // ""' "$cargoTomlJson")" 117 | CARGO_PKG_LICENSE_FILE="$(jq --raw-output '.package."license-file" // ""' "$cargoTomlJson")" 118 | export CARGO_PKG_NAME CARGO_PKG_VERSION CARGO_PKG_AUTHORS CARGO_PKG_DESCRIPTION \ 119 | CARGO_PKG_HOMEPAGE CARGO_PKG_LICENSE CARGO_PKG_LICENSE_FILE 120 | 121 | if [[ "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-([A-Za-z0-9.-]+))?(\+.*)?$ ]]; then 122 | export CARGO_PKG_VERSION_MAJOR="${BASH_REMATCH[1]}" 123 | export CARGO_PKG_VERSION_MINOR="${BASH_REMATCH[2]}" 124 | export CARGO_PKG_VERSION_PATCH="${BASH_REMATCH[3]}" 125 | export CARGO_PKG_VERSION_PRE="${BASH_REMATCH[4]}" 126 | else 127 | echo "Invalid version: $version" 128 | fi 129 | } 130 | -------------------------------------------------------------------------------- /build-rust-crate/builder-lib.sh: -------------------------------------------------------------------------------- 1 | source $stdenv/setup 2 | source $builderCommon 3 | shopt -s nullglob 4 | 5 | buildFlagsArray+=( -Cmetadata="$rustcMeta" ) 6 | 7 | configurePhase() { 8 | runHook preConfigure 9 | 10 | convertCargoToml 11 | 12 | libSrc="$(jq --raw-output '.lib.path // ""' "$cargoTomlJson")" 13 | if [[ -z "$libSrc" && -e src/lib.rs ]]; then 14 | libSrc=src/lib.rs 15 | fi 16 | if [[ ! -e "$libSrc" ]]; then 17 | echo "No library to be built" 18 | mkdir $out $dev 19 | exit 0 20 | fi 21 | 22 | crateName="$(jq --raw-output '.lib.name // (.package.name // "" | gsub("-"; "_"))' "$cargoTomlJson")" 23 | if [[ -z "$crateName" ]]; then 24 | echo "Package name must be set" 25 | exit 1 26 | fi 27 | 28 | edition="$(jq --raw-output '.package.edition // .lib.edition // ""' "$cargoTomlJson")" 29 | if [[ -n "$edition" ]]; then 30 | buildFlagsArray+=(--edition="$edition") 31 | fi 32 | 33 | mapfile -t crateTypes < <(jq --raw-output '.lib."crate-type" // ["lib"] | .[]' "$cargoTomlJson") 34 | cargoTomlIsProcMacro="$(jq --raw-output 'if .lib."proc-macro" then "1" else "" end' "$cargoTomlJson")" 35 | if [[ "$cargoTomlIsProcMacro" != "$procMacro" ]]; then 36 | echo "Cargo.toml says proc-macro = ${cargoTomlIsProcMacro:-0} but it is built with procMacro = ${procMacro:-0}" 37 | exit 1 38 | fi 39 | if [[ -n "$procMacro" ]]; then 40 | # Override crate type. 41 | crateTypes=("proc-macro") 42 | buildFlagsArray+=(--extern=proc_macro) 43 | fi 44 | 45 | needLinkDeps= 46 | buildCdylib= 47 | for crateType in "${crateTypes[@]}"; do 48 | case "$crateType" in 49 | lib|rlib) 50 | ;; 51 | dylib|staticlib|proc-macro|bin) 52 | needLinkDeps=1 53 | ;; 54 | cdylib) 55 | buildCdylib=1 56 | ;; 57 | *) 58 | echo "Unsupported crate-type: $crateType" 59 | exit 1 60 | ;; 61 | esac 62 | done 63 | if [[ -n "$needLinkDeps" ]]; then 64 | addExternFlags buildFlagsArray link $dependencies 65 | else 66 | addExternFlags buildFlagsArray meta $dependencies 67 | fi 68 | 69 | declare -a cdylibBuildFlagsArray 70 | importBuildOut buildFlagsArray cdylibBuildFlagsArray "$buildDrv" 71 | # FIXME: cargo include cdylib flags for all crate-types once cdylib is included. 72 | buildFlagsArray+=( "${cdylibBuildFlagsArray[@]}" ) 73 | 74 | addFeatures buildFlagsArray $features 75 | setCargoCommonBuildEnv 76 | export CARGO_CRATE_NAME="$crateName" 77 | 78 | collectTransDeps $dev/rust-support/deps-closure $dependencies 79 | buildFlagsArray+=(-Ldependency="$dev/rust-support/deps-closure") 80 | 81 | runHook postConfigure 82 | } 83 | 84 | buildPhase() { 85 | runHook preBuild 86 | 87 | local crateTypesCommaSep 88 | printf -v crateTypesCommaSep '%s,' "${crateTypes[@]}" 89 | crateTypesCommaSep="${crateTypesCommaSep%,}" 90 | 91 | mkdir -p $out/lib 92 | runRustc "Building lib" \ 93 | "$libSrc" \ 94 | --out-dir="$out/lib" \ 95 | --crate-name="$crateName" \ 96 | --crate-type="$crateTypesCommaSep" \ 97 | --emit=metadata,link \ 98 | -Cextra-filename="-$rustcMeta" \ 99 | $buildFlags \ 100 | "${buildFlagsArray[@]}" 101 | 102 | runHook postBuild 103 | } 104 | 105 | installPhase() { 106 | runHook preInstall 107 | 108 | mkdir -p $out $bin $dev/lib $dev/rust-support/deps-closure 109 | 110 | # May be empty. 111 | mv -t $dev/lib $out/lib/*.rmeta 2>/dev/null || true 112 | ln -sft $dev/rust-support/deps-closure $out/lib/* $dev/lib/* 2>/dev/null || true 113 | 114 | runHook postInstall 115 | } 116 | 117 | genericBuild 118 | -------------------------------------------------------------------------------- /build-rust-crate/default.nix: -------------------------------------------------------------------------------- 1 | { lib, nocargo-lib, stdenv, buildPackages, rust, toml2json, jq }: 2 | { pname 3 | , version 4 | , src 5 | , rustc ? buildPackages.rustc 6 | , links ? null 7 | # [ { rename = "foo" /* or null */; drv = ; } ] 8 | , dependencies ? [] 9 | # Normal dependencies with non empty `links`, which will propagate `DEP__` environments to build script. 10 | , linksDependencies ? dependencies 11 | , buildDependencies ? [] 12 | , features ? [] 13 | , profile ? {} 14 | , capLints ? null 15 | , buildFlags ? [] 16 | , buildScriptBuildFlags ? [] 17 | , procMacro ? false 18 | 19 | , nativeBuildInputs ? [] 20 | , propagatedBuildInputs ? [] 21 | , ... 22 | }@args: 23 | let 24 | inherit (nocargo-lib.target-cfg) platformToCfgAttrs; 25 | 26 | mkRustcMeta = dependencies: features: let 27 | deps = lib.concatMapStrings (dep: dep.drv.rustcMeta) dependencies; 28 | feats = lib.concatStringsSep ";" features; 29 | final = "${pname} ${version} ${feats} ${deps}"; 30 | in 31 | lib.substring 0 16 (builtins.hashString "sha256" final); 32 | 33 | buildRustcMeta = mkRustcMeta buildDependencies []; 34 | rustcMeta = mkRustcMeta dependencies []; 35 | 36 | # TODO: Pass target binary paths instead of drv here? 37 | mkDeps = map ({ rename, drv, ... }: lib.concatStringsSep ":" [ 38 | (toString rename) 39 | (toString drv.procMacro) 40 | drv.out 41 | drv.dev 42 | ]); 43 | toDevDrvs = map ({ drv, ... }: drv.dev); 44 | 45 | buildDeps = mkDeps buildDependencies; 46 | libDeps = mkDeps dependencies; 47 | 48 | builderCommon = ./builder-common.sh; 49 | 50 | convertBool = f: t: x: 51 | if x == true then t 52 | else if x == false then f 53 | else x; 54 | 55 | # https://doc.rust-lang.org/cargo/reference/profiles.html 56 | profileToRustcFlags = p: 57 | [] 58 | ++ lib.optional (p.opt-level or 0 != 0) "-Copt-level=${toString p.opt-level}" 59 | ++ lib.optional (p.debug or false != false) "-Cdebuginfo=${if p.debug == true then "2" else toString p.debug}" 60 | # TODO: `-Cstrip` is not handled since stdenv will always strip them. 61 | ++ lib.optional (p ? debug-assertions) "-Cdebug-assertions=${convertBool "no" "yes" p.debug-assertions}" 62 | ++ lib.optional (p ? overflow-checks) "-Coverflow-checks=${convertBool "no" "yes" p.debug-assertions}" 63 | ++ lib.optional (!procMacro && p.lto or false != false) "-Clto=${if p.lto == true then "fat" else p.lto}" 64 | ++ lib.optional (p.panic or "unwind" != "unwind") "-Cpanic=${p.panic}" 65 | # `incremental` is not useful since Nix builds in a sandbox. 66 | ++ lib.optional (p ? codegen-units) "-Ccodegen-units=${toString p.codegen-units}" 67 | ++ lib.optional (p.rpath or false) "-Crpath" 68 | 69 | ++ lib.optional (p.lto or false == false) "-Cembed-bitcode=no" 70 | ; 71 | 72 | convertProfile = p: { 73 | buildFlags = 74 | profileToRustcFlags p 75 | ++ lib.optional (capLints != null) "--cap-lints=${capLints}" 76 | ++ buildFlags; 77 | 78 | buildScriptBuildFlags = 79 | profileToRustcFlags (p.build-override or {}) 80 | ++ buildScriptBuildFlags; 81 | 82 | # Build script environments. 83 | PROFILE = p.name or null; 84 | OPT_LEVEL = p.opt-level or 0; 85 | DEBUG = p.debug or 0 != 0; 86 | }; 87 | 88 | profile' = convertProfile profile; 89 | buildProfile' = convertProfile (profile.build-override or {}); 90 | 91 | commonArgs = { 92 | inherit pname version src; 93 | 94 | nativeBuildInputs = [ toml2json jq ] ++ nativeBuildInputs; 95 | 96 | sharedLibraryExt = stdenv.hostPlatform.extensions.sharedLibrary; 97 | 98 | inherit capLints; 99 | 100 | RUSTC = "${rustc}/bin/rustc"; 101 | } // removeAttrs args [ 102 | "pname" 103 | "version" 104 | "src" 105 | "rustc" 106 | "links" 107 | "dependencies" 108 | "linksDependencies" 109 | "buildDependencies" 110 | "features" 111 | "profile" 112 | "capLints" 113 | "buildFlags" 114 | "buildScriptBuildFlags" 115 | "procMacro" 116 | "nativeBuildInputs" 117 | "propagatedBuildInputs" 118 | ]; 119 | 120 | cargoCfgs = lib.mapAttrs' (key: value: { 121 | name = "CARGO_CFG_${lib.toUpper key}"; 122 | value = if lib.isList value then lib.concatStringsSep "," value 123 | else if value == true then "" 124 | else value; 125 | }) (platformToCfgAttrs stdenv.hostPlatform); 126 | 127 | buildDrv = stdenv.mkDerivation ({ 128 | name = "rust_${pname}-${version}-build"; 129 | builder = ./builder-build-script.sh; 130 | inherit propagatedBuildInputs builderCommon features links; 131 | rustcMeta = buildRustcMeta; 132 | dependencies = buildDeps; 133 | 134 | linksDependencies = map (dep: dep.drv.buildDrv) linksDependencies; 135 | 136 | HOST = rust.toRustTarget stdenv.buildPlatform; 137 | TARGET = rust.toRustTarget stdenv.hostPlatform; 138 | 139 | # This drv links for `build_script_build`. 140 | # So include transitively propagated upstream `-sys` crates' ld dependencies. 141 | buildInputs = toDevDrvs dependencies; 142 | 143 | # Build script may produce object files and static libraries which should not be modified. 144 | dontFixup = true; 145 | 146 | } // commonArgs // cargoCfgs // buildProfile'); 147 | 148 | libDrv = stdenv.mkDerivation ({ 149 | name = "rust_${pname}-${version}"; 150 | 151 | builder = ./builder-lib.sh; 152 | outputs = [ "out" "dev" ]; 153 | inherit builderCommon buildDrv features rustcMeta procMacro; 154 | 155 | # Transitively propagate upstream `-sys` crates' ld dependencies. 156 | # Since `rlib` doesn't link. 157 | propagatedBuildInputs = toDevDrvs dependencies ++ propagatedBuildInputs; 158 | 159 | dependencies = libDeps; 160 | 161 | } // commonArgs // profile'); 162 | 163 | binDrv = stdenv.mkDerivation ({ 164 | name = "rust_${pname}-${version}-bin"; 165 | builder = ./builder-bin.sh; 166 | inherit propagatedBuildInputs builderCommon buildDrv features rustcMeta; 167 | 168 | libOutDrv = libDrv.out; 169 | libDevDrv = libDrv.dev; 170 | 171 | # This requires linking. 172 | # Include transitively propagated upstream `-sys` crates' ld dependencies. 173 | buildInputs = toDevDrvs dependencies; 174 | 175 | dependencies = libDeps; 176 | } // commonArgs // profile'); 177 | 178 | in 179 | libDrv // { 180 | build = buildDrv; 181 | bin = binDrv; 182 | } 183 | -------------------------------------------------------------------------------- /cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cache" 3 | version = "0.0.0" 4 | description = "Virtual package to reference all popular crates to be cached" 5 | 6 | # N.B. We cannot use `={version}` since cargo doesn't allow two compatible versions of 7 | # the same crate appears in the dependency tree, eg. getrandom 0.2.6 and getrandom@0.2.7. 8 | # So we use carpet specifiers to allow merging compatible versions to the highest one currently. 9 | [dependencies] 10 | addr2line = "0.17" 11 | adler = "1" 12 | ahash = "0.7" 13 | aho-corasick = "0.7" 14 | ansi_term = "0.12" 15 | anyhow = "1" 16 | arrayref = "0.3" 17 | arrayvec = "0.7" 18 | arrayvec-2 = { package = "arrayvec", version = "0.5" } 19 | async-stream = "0.3" 20 | async-stream-impl = "0.3" 21 | async-trait = "0.1" 22 | atty = "0.2" 23 | autocfg = "1" 24 | base64 = "0.13" 25 | bincode = "1" 26 | bitflags = "1" 27 | block-buffer = "0.10" 28 | block-buffer-2 = { package = "block-buffer", version = "0.9" } 29 | bstr = "0.2" 30 | byteorder = "1" 31 | bytes = "1" 32 | cc = "1" 33 | cfg-if = "1" 34 | cfg-if-2 = { package = "cfg-if", version = "0.1" } 35 | chrono = "0.4" 36 | clap = "2" 37 | clap_lex = "0.2" 38 | constant_time_eq = "0.1" 39 | convert_case = "0.4" 40 | core-foundation-sys = "0.8" 41 | cpufeatures = "0.2" 42 | crc32fast = "1" 43 | crossbeam-channel = "0.5" 44 | crossbeam-deque = "0.8" 45 | crossbeam-utils = "0.8" 46 | crunchy = "0.2" 47 | crypto-common = "0.1" 48 | csv = "1" 49 | csv-core = "0.1" 50 | darling_macro = "0.13" 51 | data-encoding = "2" 52 | derivative = "2" 53 | derive_more = "0.99" 54 | digest = "0.10" 55 | digest-2 = { package = "digest", version = "0.9" } 56 | dirs-next = "2" 57 | dirs-sys = "0.3" 58 | dirs-sys-next = "0.1" 59 | either = "1" 60 | encoding_rs = "0.8" 61 | env_logger = "0.9" 62 | fallible-iterator = "0.2" 63 | fastrand = "1" 64 | flate2 = "1" 65 | fnv = "1" 66 | foreign-types = "0.3" 67 | foreign-types-shared = "0.1" 68 | form_urlencoded = "1" 69 | futures = "0.3" 70 | futures-channel = "0.3" 71 | futures-core = "0.3" 72 | futures-executor = "0.3" 73 | futures-io = "0.3" 74 | futures-macro = "0.3" 75 | futures-sink = "0.3" 76 | futures-task = "0.3" 77 | futures-util = "0.3" 78 | generic-array = "0.14" 79 | getrandom = "0.2" 80 | getrandom-2 = { package = "getrandom", version = "0.1" } 81 | gimli = "0.26" 82 | glob = "0.3" 83 | h2 = "0.3" 84 | half = "1" 85 | hashbrown = "0.12" 86 | hashbrown-2 = { package = "hashbrown", version = "0.11" } 87 | heck = "0.4" 88 | heck-2 = { package = "heck", version = "0.3" } 89 | hermit-abi = "0.1" 90 | hex = "0.4" 91 | hmac = "0.12" 92 | hostname = "0.3" 93 | http = "0.2" 94 | http-body = "0.4" 95 | httparse = "1" 96 | httpdate = "1" 97 | humantime = "2" 98 | hyper = "0.14" 99 | hyper-tls = "0.5" 100 | ident_case = "1" 101 | idna = "0.2" 102 | indexmap = "1" 103 | instant = "0.1" 104 | ipnet = "2" 105 | itertools = "0.10" 106 | itoa = "1" 107 | itoa-2 = { package = "itoa", version = "0.4" } 108 | jobserver = "0.1" 109 | lazy_static = "1" 110 | lazycell = "1" 111 | libc = "0.2" 112 | libloading = "0.7" 113 | linked-hash-map = "0.5" 114 | lock_api = "0.4" 115 | log = "0.4" 116 | maplit = "1" 117 | match_cfg = "0.1" 118 | matchers = "0.1" 119 | matches = "0.1" 120 | memchr = "2" 121 | memoffset = "0.6" 122 | mime = "0.3" 123 | minimal-lexical = "0.2" 124 | miniz_oxide = "0.5" 125 | miniz_oxide-2 = { package = "miniz_oxide", version = "0.4" } 126 | mio = "0.8" 127 | multimap = "0.8" 128 | native-tls = "0.2" 129 | nom = "7" 130 | num-bigint = "0.4" 131 | num-integer = "0.1" 132 | num-iter = "0.1" 133 | num-traits = "0.2" 134 | num_cpus = "1" 135 | num_threads = "0.1" 136 | object = "0.29" 137 | once_cell = "1" 138 | oorandom = "11" 139 | opaque-debug = "0.3" 140 | openssl-macros = "0.1" 141 | openssl-probe = "0.1" 142 | openssl-sys = "0.9" 143 | parking_lot = "0.12" 144 | parking_lot-2 = { package = "parking_lot", version = "0.11" } 145 | parking_lot_core = "0.9" 146 | parking_lot_core-2 = { package = "parking_lot_core", version = "0.8" } 147 | peeking_take_while = "0.1" 148 | percent-encoding = "2" 149 | phf_shared = "0.10" 150 | pin-project-lite = "0.2" 151 | pin-utils = "0.1" 152 | pkg-config = "0.3" 153 | ppv-lite86 = "0.2" 154 | proc-macro-error = "1" 155 | proc-macro-error-attr = "1" 156 | proc-macro-hack = "0.5" 157 | proc-macro2 = "1" 158 | quick-error = "1" 159 | quote = "1" 160 | rand = "0.8" 161 | rand-2 = { package = "rand", version = "0.7" } 162 | rand_chacha = "0.3" 163 | rand_chacha-2 = { package = "rand_chacha", version = "0.2" } 164 | rand_core = "0.6" 165 | rand_core-2 = { package = "rand_core", version = "0.5" } 166 | rayon = "1" 167 | rayon-core = "1" 168 | regex = "1" 169 | regex-automata = "0.1" 170 | regex-syntax = "0.6" 171 | remove_dir_all = "0.5" 172 | reqwest = "0.11" 173 | ring = "0.16" 174 | rustc-demangle = "0.1" 175 | rustc-hash = "1" 176 | rustc_version = "0.4" 177 | rustc_version-2 = { package = "rustc_version", version = "0.2" } 178 | rustls = "0.20" 179 | ryu = "1" 180 | same-file = "1" 181 | scopeguard = "1" 182 | sct = "0.7" 183 | semver = "0.9" 184 | semver-parser = "0.7" 185 | serde = "1" 186 | serde_cbor = "0.11" 187 | serde_derive = "1" 188 | serde_json = "1" 189 | serde_urlencoded = "0.7" 190 | sha-1 = "0.10" 191 | sha2 = "0.10" 192 | sha2-2 = { package = "sha2", version = "0.9" } 193 | sharded-slab = "0.1" 194 | shlex = "1" 195 | signal-hook-registry = "1" 196 | siphasher = "0.3" 197 | slab = "0.4" 198 | smallvec = "1" 199 | socket2 = "0.4" 200 | spin = "0.5" 201 | stable_deref_trait = "1" 202 | static_assertions = "1" 203 | strsim = "0.10" 204 | strsim-2 = { package = "strsim", version = "0.8" } 205 | subtle = "2" 206 | syn = "1" 207 | synstructure = "0.12" 208 | tempfile = "3" 209 | termcolor = "1" 210 | terminal_size = "0.1" 211 | textwrap = "0.15" 212 | textwrap-2 = { package = "textwrap", version = "0.11" } 213 | thiserror = "1" 214 | thiserror-impl = "1" 215 | thread_local = "1" 216 | time = "0.1" 217 | time-macros = "0.2" 218 | tinyvec = "1" 219 | tinyvec_macros = "0.1" 220 | tokio-macros = "1" 221 | tokio-native-tls = "0.3" 222 | tokio-rustls = "0.23" 223 | tokio-stream = "0.1" 224 | tokio-util = "0.7" 225 | toml = "0.5" 226 | tower-layer = "0.3" 227 | tower-service = "0.3" 228 | tracing = "0.1" 229 | tracing-attributes = "0.1" 230 | tracing-core = "0.1" 231 | tracing-futures = "0.2" 232 | tracing-log = "0.1" 233 | try-lock = "0.2" 234 | typenum = "1" 235 | unicase = "2" 236 | unicode-bidi = "0.3" 237 | unicode-ident = "1" 238 | unicode-normalization = "0.1" 239 | unicode-segmentation = "1" 240 | unicode-width = "0.1" 241 | unicode-xid = "0.2" 242 | untrusted = "0.7" 243 | url = "2" 244 | uuid = "1" 245 | uuid-2 = { package = "uuid", version = "0.8" } 246 | vcpkg = "0.2" 247 | vec_map = "0.8" 248 | version_check = "0.9" 249 | void = "1" 250 | walkdir = "2" 251 | want = "0.3" 252 | wasi = "0.11" 253 | webpki = "0.22" 254 | which = "4" 255 | winapi = "0.3" 256 | winapi-i686-pc-windows-gnu = "0.4" 257 | winapi-util = "0.1" 258 | winapi-x86_64-pc-windows-gnu = "0.4" 259 | windows-sys = "0.36" 260 | windows_aarch64_msvc = "0.36" 261 | windows_i686_gnu = "0.36" 262 | windows_i686_msvc = "0.36" 263 | windows_x86_64_gnu = "0.36" 264 | windows_x86_64_msvc = "0.36" 265 | yaml-rust = "0.4" 266 | -------------------------------------------------------------------------------- /cache/default.nix: -------------------------------------------------------------------------------- 1 | { writeText, mkRustPackageOrWorkspace }: 2 | let 3 | ws = mkRustPackageOrWorkspace { 4 | src = ./.; 5 | }; 6 | in 7 | writeText "cache-paths" 8 | (toString (ws.dev.cache.dependencies ++ ws.release.cache.dependencies)) 9 | -------------------------------------------------------------------------------- /cache/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oxalica/nocargo/089f487c2c123454c8b9a381a99999b63c53d1d6/cache/src/lib.rs -------------------------------------------------------------------------------- /crates-io-override/default.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs }: 2 | let 3 | inherit (lib) optionalAttrs listToAttrs; 4 | inherit (builtins) compareVersions; 5 | procMacroOverrides = 6 | listToAttrs 7 | (map (name: { 8 | inherit name; 9 | value = _: { procMacro = true; }; 10 | }) (import ./proc-macro.nix)); 11 | in 12 | with pkgs; 13 | procMacroOverrides // 14 | { 15 | libz-sys = { features, ... }: optionalAttrs (!(features ? static)) { 16 | nativeBuildInputs = [ pkg-config ]; 17 | propagatedBuildInputs = [ zlib ]; 18 | }; 19 | 20 | openssl-sys = { features, ... }: optionalAttrs (!(features ? vendored)) { 21 | nativeBuildInputs = [ pkg-config ]; 22 | propagatedBuildInputs = [ openssl ]; 23 | }; 24 | 25 | proc-macro-hack = { version, ... }: { 26 | procMacro = compareVersions version "0.5.0" >= 0; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1701680307, 9 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1704161960, 24 | "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "63143ac2c9186be6d9da6035fa22620018c85932", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixpkgs-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "registry-crates-io": { 38 | "flake": false, 39 | "locked": { 40 | "lastModified": 1704732624, 41 | "narHash": "sha256-FWX5JX33ievm6JuIEp1lI5EPndYt8s6anU+fQBYuFKc=", 42 | "owner": "rust-lang", 43 | "repo": "crates.io-index", 44 | "rev": "4445fa0011e6ff9989985651bec57eb4cbefcadd", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "rust-lang", 49 | "repo": "crates.io-index", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "flake-utils": "flake-utils", 56 | "nixpkgs": "nixpkgs", 57 | "registry-crates-io": "registry-crates-io" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-utils.url = "github:numtide/flake-utils"; 4 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 5 | registry-crates-io = { 6 | url = "github:rust-lang/crates.io-index"; 7 | flake = false; 8 | }; 9 | }; 10 | 11 | outputs = { self, flake-utils, nixpkgs, registry-crates-io }@inputs: 12 | let 13 | supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; 14 | 15 | inherit (builtins) toJSON typeOf; 16 | inherit (nixpkgs.lib) isDerivation isFunction isAttrs mapAttrsToList listToAttrs flatten; 17 | 18 | nocargo-lib = import ./lib { inherit (nixpkgs) lib; }; 19 | 20 | in flake-utils.lib.eachSystem supportedSystems (system: 21 | let 22 | pkgs = nixpkgs.legacyPackages.${system}; 23 | defaultRegistries = { 24 | "https://github.com/rust-lang/crates.io-index" = 25 | nocargo-lib.pkg-info.mkIndex pkgs.fetchurl registry-crates-io 26 | (import ./crates-io-override { 27 | inherit (nixpkgs) lib; 28 | inherit pkgs; 29 | }); 30 | }; 31 | in rec { 32 | apps.default = { 33 | type = "app"; 34 | program = "${packages.noc}/bin/noc"; 35 | }; 36 | 37 | # Is there a better place? `naersk` places builders under `lib.${system}`. 38 | lib = rec { 39 | mkIndex = nocargo-lib.pkg-info.mkIndex pkgs.fetchurl; 40 | buildRustCrate = pkgs.callPackage ./build-rust-crate { 41 | inherit (packages) toml2json; 42 | inherit nocargo-lib; 43 | }; 44 | mkRustPackageOrWorkspace = pkgs.callPackage nocargo-lib.support.mkRustPackageOrWorkspace { 45 | inherit defaultRegistries buildRustCrate; 46 | }; 47 | }; 48 | 49 | packages = rec { 50 | default = noc; 51 | toml2json = pkgs.callPackage ./toml2json { }; 52 | noc = (lib.mkRustPackageOrWorkspace { 53 | src = ./noc; 54 | }).release.nocargo.bin; 55 | 56 | cache = pkgs.callPackage ./cache { 57 | inherit (lib) mkRustPackageOrWorkspace; 58 | }; 59 | }; 60 | 61 | checks = let 62 | okDrv = derivation { 63 | name = "success"; 64 | inherit system; 65 | builder = "/bin/sh"; 66 | args = [ "-c" ": >$out" ]; 67 | }; 68 | 69 | checkArgs = { 70 | inherit pkgs defaultRegistries; 71 | 72 | assertEq = got: expect: { 73 | __assertion = true; 74 | fn = name: 75 | if toJSON got == toJSON expect then 76 | okDrv 77 | else 78 | pkgs.runCommand name { 79 | nativeBuildInputs = [ pkgs.jq ]; 80 | got = toJSON got; 81 | expect = toJSON expect; 82 | } '' 83 | if [[ ''${#got} < 32 && ''${#expect} < 32 ]]; then 84 | echo "got: $got" 85 | echo "expect: $expect" 86 | else 87 | echo "got:" 88 | jq . <<<"$got" 89 | echo 90 | echo "expect:" 91 | jq . <<<"$expect" 92 | echo 93 | echo "diff:" 94 | diff -y <(jq . <<<"$got") <(jq . <<<"$expect") 95 | exit 1 96 | fi 97 | ''; 98 | }; 99 | }; 100 | 101 | tests = with nocargo-lib; { 102 | _0000-semver-compare = semver.semver-compare-tests; 103 | _0001-semver-req = semver.semver-req-tests; 104 | _0002-cfg-parser = target-cfg.cfg-parser-tests; 105 | _0003-cfg-eval = target-cfg.cfg-eval-tests; 106 | _0004-platform-cfg = target-cfg.platform-cfg-tests; 107 | _0005-glob = glob.glob-tests; 108 | _0006-sanitize-relative-path = support.sanitize-relative-path-tests; 109 | 110 | _0100-pkg-info-from-toml = pkg-info.pkg-info-from-toml-tests; 111 | _0101-preprocess-feature = resolve.preprocess-feature-tests; 112 | _0102-update-feature = resolve.update-feature-tests; 113 | _0103-resolve-feature = resolve.resolve-feature-tests; 114 | 115 | _0200-resolve-deps = resolve.resolve-deps-tests; 116 | _0201-build-from-src-dry = support.build-from-src-dry-tests; 117 | } // import ./tests { 118 | inherit pkgs self inputs defaultRegistries; 119 | }; 120 | 121 | flattenTests = prefix: v: 122 | if isDerivation v then { 123 | name = prefix; 124 | value = v; 125 | } else if v ? __assertion then { 126 | name = prefix; 127 | value = v.fn prefix; 128 | } else if isFunction v then 129 | flattenTests prefix (v checkArgs) 130 | else if isAttrs v then 131 | mapAttrsToList (name: flattenTests "${prefix}-${name}") v 132 | else 133 | throw "Unexpect test type: ${typeOf v}"; 134 | 135 | tests' = listToAttrs (flatten (mapAttrsToList flattenTests tests)); 136 | 137 | in tests'; 138 | }); 139 | } 140 | 141 | -------------------------------------------------------------------------------- /lib/default.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | let 3 | callLib = file: import file { inherit lib self; }; 4 | self = { 5 | glob = callLib ./glob.nix; 6 | semver = callLib ./semver.nix; 7 | target-cfg = callLib ./target-cfg.nix; 8 | 9 | pkg-info = callLib ./pkg-info.nix; 10 | resolve = callLib ./resolve.nix; 11 | support = callLib ./support.nix; 12 | }; 13 | in self 14 | -------------------------------------------------------------------------------- /lib/glob.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) match readDir split foldl'; 4 | inherit (lib) 5 | replaceStrings isString concatStrings stringLength hasPrefix substring concatStringsSep 6 | head tail init filter 7 | isAttrs attrNames mapAttrs getAttrFromPath; 8 | in 9 | rec { 10 | # We don't allow root-based glob and separator `/` or `\` inside brackets. 11 | globBracketPat = ''\[!?[^\/][^]\/]*]''; 12 | globAtomPat = ''[^[\/]|${globBracketPat}''; 13 | globPat = ''((${globAtomPat})+[\/])*(${globAtomPat})+''; 14 | 15 | # Parse a glob pattern into a list of segments. 16 | # 17 | # String -> List ({ lit: String } | { re: String } | { deep: true }) 18 | # 19 | # `lit` for simple string literal. 20 | # `re` for segment matching. 21 | # `deep` for `**` 22 | # 23 | # https://docs.rs/glob/0.3.0/glob/struct.Pattern.html 24 | # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04 25 | parseGlob = glob: 26 | let 27 | translateAtom = m: 28 | let m' = head m; in 29 | if isString m then 30 | replaceStrings 31 | [ "\\" "{" "(" ")" "^" "$" "|" "." "?" "*" "+" ] 32 | [ "/" "\\{" "\\(" "\\)" "\\^" "\\$" "\\|" "\\." "." ".*" "\\+" ] 33 | m 34 | else if hasPrefix "[!" m' then 35 | "[^" + substring 2 (stringLength m') m' 36 | else 37 | m'; 38 | 39 | translateSegment = seg: 40 | if seg == "**" then 41 | { deep = true; } 42 | else if match "[^[?*]+" seg != null then 43 | { lit = seg; } 44 | else 45 | { re = concatStrings (map translateAtom (split "(${globBracketPat})" seg)); }; 46 | 47 | segments = filter (s: isString s && s != "") (split ''/|\\'' glob); 48 | 49 | in 50 | if match globPat glob == null then 51 | throw '' 52 | Invalid glob pattern: ${glob} 53 | Note that we don't support root-based pattern and separator `/` or `\` inside brackets. 54 | '' 55 | else 56 | map translateSegment segments; 57 | 58 | # Find all paths in a tree matching a given glob pattern. 59 | # A path is represented as a list of segments. 60 | # 61 | # String -> Set -> List (List String) 62 | globMatchTree = glob: tree: 63 | let 64 | flatMap = f: foldl' (ret: x: ret ++ f x) []; 65 | 66 | go = path: pats: 67 | let 68 | pat = head pats; 69 | pats' = tail pats; 70 | curTree = getAttrFromPath path tree; 71 | keys = if isAttrs curTree then attrNames curTree else []; 72 | in 73 | if pats == [] then 74 | [ path ] 75 | else if pat ? lit then 76 | if pat.lit == "." then 77 | go path pats' 78 | else if pat.lit == ".." then 79 | if path == [] then 80 | [] 81 | else 82 | go (init path) pats' 83 | else if curTree ? ${pat.lit} then 84 | go (path ++ [ pat.lit ]) pats' 85 | else 86 | [] 87 | else if pat ? deep then # `**` 88 | go path pats' ++ 89 | flatMap (k: go (path ++ [ k ]) pats) keys # Pass `pats` to recurse into deep directories. 90 | else if pat ? re then 91 | flatMap (k: go (path ++ [ k ]) pats') 92 | (filter (k: match pat.re k != null) keys) 93 | else 94 | throw "Unreachable"; 95 | in 96 | go [] (parseGlob glob); 97 | 98 | # Find all paths inside a filesystem directory matching a given glob pattern. 99 | # A path is represented as a relative string with `/` as segment separator, eg. "" (root), "foo", "foo/bar" 100 | # 101 | # String -> Path -> List String 102 | globMatchDir = glob: dir: 103 | let 104 | pathToTree = dir: 105 | mapAttrs 106 | (k: ty: if ty == "directory" then pathToTree (dir + "/${k}") else ty) 107 | (readDir dir); 108 | ret = globMatchTree glob (pathToTree dir); 109 | in 110 | map (concatStringsSep "/") ret; 111 | 112 | glob-tests = { assertEq, ... }: { 113 | "0parse" = let 114 | assertInvalid = glob: assertEq (builtins.tryEval (parseGlob glob)) { success = false; value = false; }; 115 | assertParsed = glob: expect: assertEq (parseGlob glob) expect; 116 | in { 117 | invalid1 = assertInvalid ""; 118 | invalid2 = assertInvalid "/"; 119 | invalid3 = assertInvalid "/foo"; 120 | invalid4 = assertInvalid "foo//bar"; 121 | 122 | lit1 = assertParsed "foo" [ { lit = "foo"; } ]; 123 | lit2 = assertParsed "!.(^$){}" [ { lit = "!.(^$){}"; } ]; 124 | lit3 = assertParsed "." [ { lit = "."; } ]; 125 | lit4 = assertParsed ".." [ { lit = ".."; } ]; 126 | 127 | re1 = assertParsed "*" [ { re = ''.*''; } ]; 128 | re2 = assertParsed ".*" [ { re = ''\..*''; } ]; 129 | re3 = assertParsed "*.*" [ { re = ''.*\..*''; } ]; 130 | re4 = assertParsed "[[][]][![][!]][a-z0-]" [ { re = ''[[][]][^[][^]][a-z0-]''; } ]; 131 | re5 = assertParsed "?.*[[][?.*]?.*" [ { re = ''.\..*[[][?.*].\..*''; } ]; 132 | re6 = assertParsed ".[.]" [ { re = ''\.[.]''; } ]; 133 | 134 | deep1 = assertParsed "**" [ { deep = true; } ]; 135 | 136 | compound1 = assertParsed 137 | "./foo/**/*.nix" 138 | [ { lit = "."; } { lit = "foo"; } { deep = true; } { re = ''.*\.nix''; } ]; 139 | compound2 = assertParsed 140 | ".*/../log/[!abc]*-[0-9T:-]+0000.log" 141 | [ { re = ''\..*''; } { lit = ".."; } { lit = "log"; } { re = ''[^abc].*-[0-9T:-]\+0000\.log''; } ]; 142 | }; 143 | 144 | "1match-tree" = let 145 | tree = { 146 | a = null; 147 | b = null; 148 | b1 = null; 149 | b2 = null; 150 | bcd = null; 151 | bed = null; 152 | c = { 153 | d.e = { 154 | af = null; 155 | f = null; 156 | }; 157 | g.h = null; 158 | wtf = null; 159 | }; 160 | f = null; 161 | z = { 162 | a = null; 163 | b = { 164 | c = null; 165 | d.e = null; 166 | }; 167 | }; 168 | }; 169 | 170 | assertMatch = glob: expect: 171 | let 172 | ret = globMatchTree glob tree; 173 | ret' = map (concatStringsSep "/") ret; 174 | in 175 | assertEq ret' expect; 176 | 177 | in { 178 | exact1 = assertMatch "a" [ "a" ]; 179 | exact2 = assertMatch "c/g/h" [ "c/g/h" ]; 180 | exact3 = assertMatch "c/g" [ "c/g" ]; 181 | 182 | dot1 = assertMatch "./a" [ "a" ]; 183 | dot2 = assertMatch "./a/../c/g/./h" [ "c/g/h" ]; 184 | 185 | re1 = assertMatch "b*" [ "b" "b1" "b2" "bcd" "bed" ]; 186 | re2 = assertMatch "b?" [ "b1" "b2" ]; 187 | re3 = assertMatch "c/*" [ "c/d" "c/g" "c/wtf" ]; 188 | re4 = assertMatch "b?d" [ "bcd" "bed" ]; 189 | 190 | deep1 = assertMatch "**/b?d" [ "bcd" "bed" ]; 191 | deep2 = assertMatch "c/**/*f" [ "c/wtf" "c/d/e/af" "c/d/e/f" ]; 192 | deep3 = assertMatch "**/f/.." [ "" "c/d/e" ]; 193 | deep4 = assertMatch "[wz]/**" [ "z" "z/a" "z/b" "z/b/c" "z/b/d" "z/b/d/e" ]; 194 | }; 195 | 196 | "2match-dir" = { 197 | compound1 = assertEq (globMatchDir "*-*.nix" ./.) [ "pkg-info.nix" "target-cfg.nix" ]; 198 | compound2 = assertEq (globMatchDir "./features/../**/tokio-[!wtf][opq]?" ../tests) [ "tokio-app" ]; 199 | }; 200 | }; 201 | } 202 | -------------------------------------------------------------------------------- /lib/pkg-info.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) readFile readDir fromJSON fromTOML toString attrNames match; 4 | inherit (lib) 5 | stringLength splitString replaceStrings substring isString toLower 6 | filter listToAttrs mapAttrs mapAttrsToList optionalAttrs warnIf; 7 | in 8 | rec { 9 | toPkgId = { name, version, source ? null, ... }: 10 | if source != null then 11 | "${name} ${version} (${source})" 12 | else 13 | # Local crates must be collide names. Simply use the name to make overriding easier. 14 | name; 15 | 16 | mkIndex = fetchurl: path: overrides: let 17 | # TODO: We currently only support legacy format used by crates.io-index. 18 | # https://github.com/rust-lang/cargo/blob/2f3df16921deb34a92700f4d5a7ecfb424739558/src/cargo/sources/registry/mod.rs#L230-L244 19 | downloadEndpoint = (fromJSON (readFile (path + "/config.json"))).dl; 20 | mkDownloadUrl = 21 | assert match ".*\\{.*" downloadEndpoint == null; 22 | { name, version, ... }: "${downloadEndpoint}/${name}/${version}/download"; 23 | 24 | mkSrc = { name, version, sha256 }@args: fetchurl { 25 | # Use the same name as nixpkgs to benifit from cache. 26 | # https://github.com/NixOS/nixpkgs/pull/122158/files#diff-eb8b8729bfd36f8878c2d8a99f67a2bebb912e9f78c5d2a72457b1f572e26986R67 27 | name = "crate-${name}-${version}.tar.gz"; 28 | url = mkDownloadUrl args; 29 | inherit sha256; 30 | }; 31 | 32 | go = path: 33 | mapAttrs (k: v: 34 | if v == "directory" 35 | then go (path + "/${k}") 36 | else mkPkgInfoSet mkSrc k (readFile (path + "/${k}")) (overrides.${k} or null) 37 | ) (removeAttrs (readDir path) [ "config.json" ]); 38 | in 39 | go path // { __registry_index = true; }; 40 | 41 | # Get pkg info of the given package, with overrides applied if exists. 42 | getPkgInfoFromIndex = index: { name, version, checksum ? null, ... }: let 43 | name' = toLower name; 44 | len = stringLength name'; 45 | crate = 46 | if len == 1 then 47 | index."1".${name'} or null 48 | else if len == 2 then 49 | index."2".${name'} or null 50 | else if len == 3 then 51 | index."3".${substring 0 1 name'}.${name'} or null 52 | else 53 | index.${substring 0 2 name'}.${substring 2 2 name'}.${name'} or null; 54 | info = crate.${version} or null; 55 | in 56 | if !(index ? __registry_index) then 57 | throw "Invalid registry. Do you forget `mkIndex` on registry paths?" 58 | else if crate == null then 59 | throw "Package ${name} is not found in index" 60 | else if info == null then 61 | throw "Package ${name} doesn't have version ${version} in index. Available versions: ${toString (attrNames crate)}" 62 | else if info.sha256 != null && checksum != null && info.sha256 != checksum then 63 | throw "Package ${name} ${version} hash mismatched, expect ${info.sha256}, got ${checksum}" 64 | else 65 | info; 66 | 67 | # Make a set of pkg infos keyed by version. 68 | mkPkgInfoSet = mkSrc: name: content: override: let 69 | lines = filter (line: line != "") (splitString "\n" content); 70 | parseLine = line: let parsed = fromJSON line; in { 71 | name = parsed.vers; 72 | value = mkPkgInfoFromRegistry mkSrc parsed 73 | // optionalAttrs (override != null) { 74 | # Proc macro crates behave differently in dependency resolution. 75 | procMacro = (override { inherit (parsed) version; features = { }; }).procMacro or false; 76 | __override = override; 77 | }; 78 | }; 79 | in 80 | listToAttrs (map parseLine lines); 81 | 82 | # Package info: 83 | # { 84 | # name = "libz-sys"; # The name in registry. 85 | # version = "0.1.0"; # Semver. 86 | # src = ; # Source path. 87 | # sha256 = "123456...."; # Hash of the `src` tarball. (null or string) 88 | # yanked = false; # Whether it's yanked. 89 | # links = "z"; # The native library to link. (null or string) 90 | # procMacro = false; # Whether this is a proc-macro library. See comments below. 91 | # features = { # Features provided. 92 | # default = [ "std" ]; 93 | # std = []; 94 | # }; 95 | # dependencies = [ 96 | # { 97 | # name = "libc"; # Reference name. 98 | # package = "libc"; # Dependency's name in registry. (default to be `name`) 99 | # req = "^0.1.0"; # Semver requirement. 100 | # features = [ "foo" ]; # Enabled features. 101 | # optional = false; # Whether this dependency is optional. 102 | # default_features = true; # Whether to enable default features. 103 | # target = "cfg(...)"; # Only required on some targets. (null or string, default to be null) 104 | # kind = "normal"; # Dependencies (one of "normal", "dev", "build", default to be "normal") 105 | # # `registry` and `public` are not supported. 106 | # } 107 | # ]; 108 | # } 109 | mkPkgInfoFromRegistry = 110 | mkSrc: 111 | # https://github.com/rust-lang/cargo/blob/2f3df16921deb34a92700f4d5a7ecfb424739558/src/cargo/sources/registry/mod.rs#L259 112 | { name, vers, deps, features, cksum, yanked ? false, links ? null, v ? 1, ... }: 113 | if v != 1 then 114 | throw "${name} ${vers}: Registry layout version ${toString v} is too new to understand" 115 | else 116 | { 117 | inherit name features yanked links; 118 | version = vers; 119 | sha256 = cksum; 120 | dependencies = map sanitizeDep deps; 121 | # N.B. Proc macro indicator is not in the registry: https://github.com/rust-lang/cargo/issues/9605 122 | # This would be overrided in `mkPkgInfoSet`. 123 | procMacro = false; 124 | src = mkSrc { 125 | inherit name; 126 | version = vers; 127 | sha256 = cksum; 128 | }; 129 | }; 130 | 131 | # Sanitize a dependency reference. 132 | # Handling `package` and fill missing fields. 133 | sanitizeDep = 134 | { name 135 | , package ? name 136 | , version ? null # Cargo.toml use `version` 137 | , req ? version 138 | , features ? [] 139 | , optional ? false 140 | , default_features ? true 141 | , target ? null 142 | , kind 143 | , ... 144 | }@args: args // { 145 | inherit name package req features optional default_features target kind; 146 | 147 | # Note that `package` == `name` is not the same as omitting `package`. 148 | # See: https://github.com/rust-lang/cargo/issues/6827 149 | # Here we let `package` fallback to name, but set a special `rename` to the renamed `name` 150 | # if `package` != `name`. `rename` will affect the `--extern` flags. 151 | # 152 | # For remind: 153 | # - `name` is used for coresponding feature name for optional dependencies. 154 | # - `package` is used for the original package name of dependency crate. 155 | # - If `package` isn't set, the code name (for `use` or `extern crate`) of the dependency is its lib name. 156 | # `--extern` also use its own lib name. 157 | # - If `package` is set, the code name and `--extern` both use the renamed `name`. 158 | } // optionalAttrs (args.package or null != null) { 159 | rename = replaceStrings ["-"] ["_"] name; 160 | }; 161 | 162 | # Build a simplified crate into from a parsed Cargo.toml. 163 | mkPkgInfoFromCargoToml = { lockVersion ? 3, package, features ? {}, target ? {}, ... }@args: src: let 164 | transDeps = target: kind: 165 | mapAttrsToList (name: v: 166 | { 167 | inherit name target kind; 168 | package = v.package or name; 169 | # For path or git dependencies, `version` can be omitted. 170 | req = if isString v then v else v.version or null; 171 | features = v.features or []; 172 | optional = v.optional or false; 173 | # It's `default-features` in Cargo.toml, but `default_features` in index and in pkg info. 174 | default_features = 175 | warnIf (v ? default_features) "Ignoring `default_features`. Do you mean `default-features`?" 176 | (v.default-features or true); 177 | 178 | # This is used for dependency resoving inside Cargo.lock. 179 | source = 180 | if v ? registry then 181 | throw "Dependency with `registry` is not supported. Use `registry-index` with explicit URL instead." 182 | else if v ? registry-index then 183 | "registry+${v.registry-index}" 184 | else if v ? git then 185 | # For v1 and v2, git-branch URLs are encoded as "git+url" with no query parameters. 186 | if v ? branch && lockVersion >= 3 then 187 | "git+${v.git}?branch=${v.branch}" 188 | else if v ? tag then 189 | "git+${v.git}?tag=${v.tag}" 190 | else if v ? rev then 191 | "git+${v.git}?rev=${v.rev}" 192 | else 193 | "git+${v.git}" 194 | else if v ? path then 195 | # Local crates are mark with `null` source. 196 | null 197 | else 198 | # Default to use crates.io registry. 199 | # N.B. This is necessary and must not be `null`, or it will be indinstinguishable 200 | # with local crates or crates from other registries. 201 | "registry+https://github.com/rust-lang/crates.io-index"; 202 | 203 | # See `sanitizeDep` 204 | } // optionalAttrs (v.package or null != null) { 205 | rename = replaceStrings ["-"] ["_"] name; 206 | }); 207 | 208 | collectTargetDeps = target: { dependencies ? {}, dev-dependencies ? {}, build-dependencies ? {}, ... }: 209 | transDeps target "normal" dependencies ++ 210 | transDeps target "dev" dev-dependencies ++ 211 | transDeps target "build" build-dependencies; 212 | 213 | in 214 | { 215 | inherit (package) name version; 216 | inherit src features; 217 | links = package.links or null; 218 | procMacro = args.lib.proc-macro or false; 219 | dependencies = 220 | collectTargetDeps null args ++ 221 | mapAttrsToList collectTargetDeps target; 222 | }; 223 | 224 | pkg-info-from-toml-tests = { assertEq, ... }: { 225 | simple = let 226 | cargoToml = fromTOML (readFile ../tests/tokio-app/Cargo.toml); 227 | info = mkPkgInfoFromCargoToml cargoToml ""; 228 | 229 | expected = { 230 | name = "tokio-app"; 231 | version = "0.0.0"; 232 | features = { }; 233 | src = ""; 234 | links = null; 235 | procMacro = false; 236 | dependencies = [ 237 | { 238 | name = "tokio"; 239 | package = "tokio"; 240 | default_features = false; 241 | features = [ "rt-multi-thread" "macros" "time" ]; 242 | kind = "normal"; 243 | optional = false; 244 | req = "1"; 245 | target = null; 246 | source = "registry+https://github.com/rust-lang/crates.io-index"; 247 | } 248 | ]; 249 | }; 250 | in 251 | assertEq info expected; 252 | 253 | build-deps = 254 | let 255 | cargoToml = fromTOML (readFile ../tests/build-deps/Cargo.toml); 256 | info = mkPkgInfoFromCargoToml cargoToml ""; 257 | expected = { 258 | name = "build-deps"; 259 | version = "0.0.0"; 260 | features = { }; 261 | src = ""; 262 | links = null; 263 | procMacro = false; 264 | dependencies = [ 265 | { 266 | name = "semver"; 267 | package = "semver"; 268 | default_features = true; 269 | features = [ ]; 270 | kind = "build"; 271 | optional = false; 272 | req = "1"; 273 | target = null; 274 | source = "registry+https://github.com/rust-lang/crates.io-index"; 275 | } 276 | ]; 277 | }; 278 | in 279 | assertEq info expected; 280 | }; 281 | } 282 | -------------------------------------------------------------------------------- /lib/resolve.nix: -------------------------------------------------------------------------------- 1 | { lib, self }: 2 | let 3 | inherit (builtins) readFile match fromTOML toJSON; 4 | inherit (lib) 5 | foldl' concatStringsSep listToAttrs filter elemAt length optional sort elem flatten 6 | hasPrefix substring 7 | attrValues mapAttrs attrNames filterAttrs composeManyExtensions assertMsg; 8 | inherit (self.semver) parseSemverReq; 9 | inherit (self.pkg-info) mkPkgInfoFromCargoToml toPkgId sanitizeDep; 10 | in rec { 11 | 12 | # Resolve the dependencies graph based on the lock file. 13 | # Output: 14 | # { 15 | # "libz-sys 0.1.0 (https://...)" = { 16 | # # name, sha256, ... All fields from pkg info. 17 | # dependencies = [ 18 | # { 19 | # # name, kind, ... All fields from dependency in the pkg info. 20 | # resolved = "libc 0.1.0 (https://...)"; 21 | # }; 22 | # }; 23 | # }; 24 | # } 25 | # 26 | # Currently (rust 1.63.0), there are 3 versions of the lock file. 27 | # We supports V1, V2 and V3. 28 | # See: 29 | # https://github.com/rust-lang/cargo/blob/rust-1.63.0/src/cargo/core/resolver/resolve.rs#L56 30 | # https://github.com/rust-lang/cargo/blob/rust-1.63.0/src/cargo/core/resolver/encode.rs 31 | resolveDepsFromLock = getPkgInfo: lock: let 32 | # For git sources, they are referenced without the locked hash part after `#`. 33 | # Define: "git+https://github.com/dtolnay/semver?tag=1.0.4#ea9ea80c023ba3913b9ab0af1d983f137b4110a5" 34 | # Reference: "semver 1.0.4 (git+https://github.com/dtolnay/semver?tag=1.0.4)" 35 | removeUrlHash = s: 36 | let m = match "([^#]*)#.*" s; in 37 | if m == null then s else elemAt m 0; 38 | 39 | pkgs = map 40 | (pkg: if pkg ? source then pkg // { source = removeUrlHash pkg.source; } else pkg) 41 | lock.package; 42 | 43 | pkgsByName = foldl' (set: { name, ... }@pkg: 44 | set // { ${name} = (set.${name} or []) ++ [ pkg ]; } 45 | ) {} pkgs; 46 | 47 | resolved = listToAttrs (map resolvePkg pkgs); 48 | 49 | resolvePkg = { name, version, source ? "", dependencies ? [], ... }@args: let 50 | info = getPkgInfo args; 51 | candidates = map findPkgId dependencies; 52 | 53 | id = toPkgId args; 54 | resolvedDependencies = 55 | map (dep: dep // { 56 | resolved = selectDep candidates dep; 57 | }) info.dependencies; 58 | 59 | # Find the exact package id of a dependency key, which may omit version or source. 60 | findPkgId = key: let 61 | m = match "([^ ]+)( ([^ ]+))?( \\(([^\\)]*)\\))?" key; 62 | lockName = elemAt m 0; 63 | lockVersion = elemAt m 2; 64 | lockSource = elemAt m 4; 65 | candidates = 66 | filter (pkg: 67 | (lockVersion != null -> pkg.version == lockVersion) && 68 | (lockSource != null -> pkg.source or null == lockSource)) 69 | (pkgsByName.${lockName} or []); 70 | candidateCnt = length candidates; 71 | in 72 | if candidateCnt == 0 then 73 | throw "When resolving ${id}, locked dependency `${key}` not found" 74 | else if candidateCnt > 1 then 75 | throw '' 76 | When resolving ${id}, locked dependency `${key}` is ambiguous. 77 | Found: ${toJSON candidates} 78 | '' 79 | else 80 | elemAt candidates 0; 81 | 82 | selectDep = candidates: { name, package, req, source ? null, ... }: 83 | let 84 | # Local path or git dependencies don't have version req. 85 | checkReq = if req != null then parseSemverReq req else (ver: true); 86 | checkSource = if source != null then s: s == source else s: true; 87 | 88 | selected = filter 89 | ({ name, version, source ? null, ... }: name == package && checkReq version && checkSource source) 90 | candidates; 91 | 92 | selectedCnt = length selected; 93 | in 94 | if selectedCnt == 0 then 95 | # Cargo will omit disabled optional dependencies in lock file. 96 | # throw "When resolving ${pkgName} ${crateVersion}, dependency ${package} ${req} isn't satisfied in lock file" 97 | null 98 | else if selectedCnt > 1 then 99 | throw '' 100 | When resolving ${id}, dependency ${package} ${if req == null then "*" else req} has multiple candidates in lock file. 101 | Found: ${toJSON selected} 102 | '' 103 | else 104 | toPkgId (elemAt selected 0); 105 | 106 | in 107 | { 108 | name = toPkgId args; 109 | value = info // { dependencies = resolvedDependencies; }; 110 | }; 111 | 112 | in 113 | assert assertMsg (lock.version or 3 == 3) "Unsupported version of Cargo.lock: ${toString lock.version}"; 114 | resolved; 115 | 116 | # Calculate the closure of each feature, with `dep:pkg` and `pkg?/feat` syntax desugared. 117 | # [String] -> { [String] } -> { [String | { dep: String, feat?: String }] } 118 | preprocessFeatures = optionalDeps: defs: let 119 | allRefs = flatten (attrValues defs); 120 | defs' = 121 | listToAttrs 122 | (map (dep: { name = dep; value = [ "dep:${dep}" ]; }) 123 | (filter (dep: !elem "dep:${dep}" allRefs) 124 | optionalDeps)) 125 | // defs; 126 | go = prev: feat: 127 | let 128 | m = match "([a-zA-Z0-9]+)(\\?)?/([a-zA-Z0-9]+)" feat; 129 | depName = elemAt m 0; 130 | isWeak = elemAt m 1 != null; 131 | depFeat = elemAt m 2; 132 | in if elem feat prev then 133 | prev 134 | else if defs' ? ${feat} then 135 | foldl' go ([ feat ] ++ prev) defs'.${feat} 136 | else if hasPrefix "dep:" feat then 137 | [ { dep = substring 4 (-1) feat; } ] ++ prev 138 | else if m == null then 139 | [ feat ] ++ prev 140 | else if isWeak then 141 | [ { dep = depName; feat = depFeat; } ] ++ prev 142 | else 143 | [ { dep = depName; } { dep = depName; feat = depFeat; } ] ++ prev; 144 | fixed = mapAttrs (feat: _: go [ ] feat) defs'; 145 | in 146 | fixed; 147 | 148 | # Enable `features` in `prev` and do recursive update according to `defs`. 149 | # Optional dependencies must be included in `defs`. 150 | enableFeatures = pkgId: defs: prev: features: 151 | foldl' (prev: feat: let 152 | m = match "(.*)/.*" feat; 153 | mDep = elemAt m 0; 154 | nexts = 155 | if m == null then 156 | # Must be defined. 157 | defs.${feat} or (throw '' 158 | Feature '${feat}' is invalid for ${pkgId}. 159 | Possible features: ${concatStringsSep "," (attrNames defs)} 160 | '') 161 | else 162 | # Dependent features implies optional dependency to be enabled. 163 | # But non-optional dependency doesn't have coresponding feature flag. 164 | optional (defs ? ${mDep}) mDep; 165 | in 166 | if prev.${feat} or false then 167 | prev 168 | else 169 | enableFeatures pkgId defs (prev // { ${feat} = true; }) nexts 170 | ) prev features; 171 | 172 | # Resolve all features. 173 | # Note that dependent features like `foo/bar` are only available during resolution, 174 | # and will be removed in result set. 175 | # 176 | # Returns: 177 | # { 178 | # "libc 0.1.0 (https://...)" = [ "default" "foo" "bar" ]; 179 | # } 180 | resolveFeatures = { 181 | # Follows the layout of the output of `resolveDepsFromLock`. 182 | pkgSet 183 | # Dependency edges (`{ name, kind, resolved, ... }`) will be checked by this filter. 184 | # Only edges returning `true` are considered and propagated. 185 | , depFilter ? dep: true 186 | # Eg. "libc 0.1.0 (https://...)" 187 | , rootId 188 | # Eg. [ "foo" "bar/baz" ] 189 | , rootFeatures 190 | }: let 191 | 192 | featureDefs = mapAttrs (id: { features, dependencies, ... }: 193 | features // 194 | listToAttrs 195 | (map (dep: { name = dep.name; value = []; }) 196 | # We should collect all optional dependencies for feature def, even though they are not selected. 197 | # This happens on `rand@0.8.3`, whose `default` feature enables `rand_hc`, which is only available 198 | # for `cfg(target_os = "emscripten")`. This feature should be still enable, though optional dependency 199 | # is not. 200 | (filter (dep: dep.optional) dependencies)) 201 | ) pkgSet; 202 | 203 | # initialFeatures = mapAttrs (id: defs: mapAttrs (k: v: false) defs) featureDefs; 204 | initialFeatures = mapAttrs (id: info: {}) pkgSet; 205 | 206 | # Overlay of spreading 's nested features into dependencies and enable optional dependencies. 207 | updateDepsOverlay = id: final: prev: let 208 | info = pkgSet.${id}; 209 | finalFeatures = final.${id} or {}; 210 | updateDep = { name, optional, resolved, default_features, features, ... }: final: prev: let 211 | depFeatures = 212 | lib.optional (default_features && featureDefs.${resolved} ? default) "default" ++ 213 | features ++ 214 | filter (feat: feat != null) 215 | (map (feat: let m = match "(.*)/(.*)" feat; in 216 | if m != null && elemAt m 0 == name then 217 | elemAt m 1 218 | else 219 | null 220 | ) (attrNames finalFeatures)); 221 | in 222 | { 223 | ${resolved} = 224 | # This condition must be evaluated under `${resolved} =`, 225 | # or we'll enter an infinite recursion. 226 | if optional -> finalFeatures.${name} or false then 227 | enableFeatures 228 | resolved 229 | featureDefs.${resolved} 230 | prev.${resolved} 231 | depFeatures 232 | else 233 | prev.${resolved}; 234 | }; 235 | in 236 | composeManyExtensions 237 | (map updateDep 238 | (filter depFilter info.dependencies)) 239 | final 240 | prev; 241 | 242 | rootOverlay = final: prev: { 243 | ${rootId} = enableFeatures 244 | rootId 245 | featureDefs.${rootId} 246 | initialFeatures.${rootId} 247 | rootFeatures; 248 | }; 249 | 250 | final = 251 | composeManyExtensions 252 | (map updateDepsOverlay (attrNames pkgSet) ++ [ rootOverlay ]) 253 | final 254 | initialFeatures; 255 | 256 | final' = 257 | mapAttrs (id: feats: filter (feat: match ".*/.*" feat == null) (attrNames feats)) final; 258 | 259 | in 260 | final'; 261 | 262 | preprocess-feature-tests = { assertEq, ... }: let 263 | test = optionalDeps: featureDefs: expect: 264 | assertEq (preprocessFeatures optionalDeps featureDefs) expect; 265 | in { 266 | recursive = test [ ] { a = [ "b" ]; b = [ "a" "c" ]; c = [ ]; } { 267 | a = [ "c" "b" "a" ]; 268 | b = [ "c" "a" "b" ]; 269 | c = [ "c" ]; 270 | }; 271 | auto-dep = test [ "a" ] { b = [ "a" ]; } { 272 | a = [ { dep = "a"; } "a" ]; 273 | b = [ { dep = "a"; } "a" "b" ]; 274 | }; 275 | manual-dep = test [ "a" ] { b = [ "dep:a" ]; } { 276 | b = [ { dep = "a"; } "b" ]; 277 | }; 278 | strong-dep = test [ "a" ] { b = [ "a/c" ]; } { 279 | a = [ { dep = "a"; } "a" ]; 280 | b = [ { dep = "a"; } { dep = "a"; feat = "c"; } "b" ]; 281 | }; 282 | weak-dep = test [ "a" ] { b = [ "a?/c" ]; } { 283 | a = [ { dep = "a"; } "a" ]; 284 | b = [ { dep = "a"; feat = "c"; } "b" ]; 285 | }; 286 | }; 287 | 288 | update-feature-tests = { assertEq, ... }: let 289 | testUpdate = defs: features: expect: let 290 | init = mapAttrs (k: v: false) defs; 291 | out = enableFeatures "pkgId" defs init features; 292 | enabled = attrNames (filterAttrs (k: v: v) out); 293 | in 294 | assertEq enabled expect; 295 | in { 296 | simple1 = testUpdate { a = []; } [] []; 297 | simple2 = testUpdate { a = []; } [ "a" ] [ "a" ]; 298 | simple3 = testUpdate { a = []; } [ "a" "a" ] [ "a" ]; 299 | simple4 = testUpdate { a = []; b = []; } [ "a" ] [ "a" ]; 300 | simple5 = testUpdate { a = []; b = []; } [ "a" "b" ] [ "a" "b" ]; 301 | simple6 = testUpdate { a = []; b = []; } [ "a" "b" "a" ] [ "a" "b" ]; 302 | 303 | } // (let defs = { a = []; b = [ "a" ]; }; in { 304 | link1 = testUpdate defs [ "a" ] [ "a" ]; 305 | link2 = testUpdate defs [ "b" "a" ] [ "a" "b" ]; 306 | link3 = testUpdate defs [ "b" ] [ "a" "b" ]; 307 | link4 = testUpdate defs [ "b" "a" ] [ "a" "b" ]; 308 | link5 = testUpdate defs [ "b" "b" ] [ "a" "b" ]; 309 | 310 | }) // (let defs = { a = []; b = [ "a" ]; c = [ "a" ]; }; in { 311 | common1 = testUpdate defs [ "a" ] [ "a" ]; 312 | common2 = testUpdate defs [ "b" ] [ "a" "b" ]; 313 | common3 = testUpdate defs [ "a" "b" ] [ "a" "b" ]; 314 | common4 = testUpdate defs [ "b" "a" ] [ "a" "b" ]; 315 | common5 = testUpdate defs [ "b" "c" ] [ "a" "b" "c" ]; 316 | common6 = testUpdate defs [ "a" "b" "c" ] [ "a" "b" "c" ]; 317 | common7 = testUpdate defs [ "b" "c" "b" ] [ "a" "b" "c" ]; 318 | 319 | }) // (let defs = { a = [ "b" "c" ]; b = [ "d" "e" ]; c = [ "f" "g"]; d = []; e = []; f = []; g = []; }; in { 320 | tree1 = testUpdate defs [ "a" ] [ "a" "b" "c" "d" "e" "f" "g" ]; 321 | tree2 = testUpdate defs [ "b" ] [ "b" "d" "e" ]; 322 | tree3 = testUpdate defs [ "d" ] [ "d" ]; 323 | tree4 = testUpdate defs [ "d" "b" "g" ] [ "b" "d" "e" "g" ]; 324 | tree5 = testUpdate defs [ "c" "e" "f" ] [ "c" "e" "f" "g" ]; 325 | 326 | }) // (let defs = { a = [ "b" ]; b = [ "c" ]; c = [ "b" ]; }; in { 327 | cycle1 = testUpdate defs [ "b" ] [ "b" "c" ]; 328 | cycle2 = testUpdate defs [ "c" ] [ "b" "c" ]; 329 | cycle3 = testUpdate defs [ "a" ] [ "a" "b" "c" ]; 330 | }); 331 | 332 | resolve-feature-tests = { assertEq, ... }: let 333 | test = pkgSet: rootId: rootFeatures: expect: let 334 | resolved = resolveFeatures { inherit pkgSet rootId rootFeatures; }; 335 | expect' = mapAttrs (id: feats: sort (a: b: a < b) feats) expect; 336 | in 337 | assertEq resolved expect'; 338 | 339 | pkgSet1 = { 340 | a = { 341 | features = { foo = [ "bar" ]; bar = []; baz = [ "b" ]; }; 342 | dependencies = [ 343 | { name = "b"; resolved = "b-id"; optional = true; default_features = true; features = [ "a" ]; } 344 | { name = "unused"; resolved = null; optional = true; default_features = true; features = []; } 345 | ]; 346 | }; 347 | b-id = { 348 | features = { default = []; foo = []; bar = [ "foo" ]; a = []; }; 349 | dependencies = []; 350 | }; 351 | }; 352 | 353 | pkgSet2 = { 354 | my-id = { 355 | features = { default = [ "tokio/macros" ]; }; 356 | dependencies = [ 357 | { name = "tokio"; resolved = "tokio-id"; optional = false; default_features = false; features = [ "fs" ]; } 358 | { name = "dep"; resolved = "dep-id"; optional = false; default_features = true; features = []; } 359 | ]; 360 | }; 361 | dep-id = { 362 | features = { default = [ "tokio/sync" ]; }; 363 | dependencies = [ 364 | { name = "tokio"; resolved = "tokio-id"; optional = false; default_features = false; features = [ "sync" ]; } 365 | ]; 366 | }; 367 | tokio-id = { 368 | features = { default = []; fs = []; sync = []; macros = []; io = []; }; 369 | dependencies = []; 370 | }; 371 | }; 372 | 373 | in { 374 | simple = test pkgSet1 "a" [ "foo" ] { 375 | a = [ "foo" "bar" ]; 376 | b-id = [ ]; 377 | }; 378 | 379 | depend = test pkgSet1 "a" [ "foo" "baz" ] { 380 | a = [ "foo" "bar" "baz" "b" ]; 381 | b-id = [ "default" "a" ]; 382 | }; 383 | 384 | override = test pkgSet1 "a" [ "b/bar" ] { 385 | a = [ "b" ]; 386 | b-id = [ "default" "a" "bar" "foo" ]; 387 | }; 388 | 389 | merge = test pkgSet2 "my-id" [ "default" ] { 390 | my-id = [ "default" ]; 391 | dep-id = [ "default" ]; 392 | tokio-id = [ "fs" "sync" "macros" ]; 393 | }; 394 | }; 395 | 396 | resolve-deps-tests = { assertEq, defaultRegistries, ... }: { 397 | simple = let 398 | index = { 399 | libc."0.1.12" = { name = "libc"; version = "0.1.12"; dependencies = []; }; 400 | libc."0.2.95" = { name = "libc"; version = "0.2.95"; dependencies = []; }; 401 | testt."0.1.0" = { 402 | name = "testt"; 403 | version = "0.1.0"; 404 | dependencies = map sanitizeDep [ 405 | { name = "libc"; req = "^0.1.0"; kind = "normal"; } 406 | { name = "liba"; package = "libc"; req = "^0.2.0"; kind = "normal"; } 407 | ]; 408 | }; 409 | }; 410 | 411 | lock = { 412 | package = [ 413 | { 414 | name = "libc"; 415 | version = "0.1.12"; 416 | source = "registry+https://github.com/rust-lang/crates.io-index"; 417 | } 418 | { 419 | name = "libc"; 420 | version = "0.2.95"; 421 | source = "registry+https://github.com/rust-lang/crates.io-index"; 422 | } 423 | { 424 | name = "testt"; 425 | version = "0.1.0"; 426 | dependencies = [ 427 | "libc 0.1.12" 428 | "libc 0.2.95 (registry+https://github.com/rust-lang/crates.io-index)" 429 | ]; 430 | } 431 | ]; 432 | }; 433 | 434 | expected = { 435 | "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = { 436 | name = "libc"; 437 | version = "0.1.12"; 438 | dependencies = [ ]; 439 | }; 440 | "libc 0.2.95 (registry+https://github.com/rust-lang/crates.io-index)" = { 441 | name = "libc"; 442 | version = "0.2.95"; 443 | dependencies = [ ]; 444 | }; 445 | "testt" = { 446 | name = "testt"; 447 | version = "0.1.0"; 448 | dependencies = [ 449 | { 450 | name = "libc"; 451 | package = "libc"; 452 | req = "^0.1.0"; 453 | resolved = "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)"; 454 | kind = "normal"; 455 | optional = false; 456 | features = []; 457 | default_features = true; 458 | target = null; 459 | } 460 | { 461 | name = "liba"; 462 | rename = "liba"; 463 | package = "libc"; 464 | req = "^0.2.0"; 465 | resolved = "libc 0.2.95 (registry+https://github.com/rust-lang/crates.io-index)"; 466 | kind = "normal"; 467 | optional = false; 468 | features = []; 469 | default_features = true; 470 | target = null; 471 | } 472 | ]; 473 | }; 474 | }; 475 | 476 | getPkgInfo = { name, version, ... }: index.${name}.${version}; 477 | resolved = resolveDepsFromLock getPkgInfo lock; 478 | in 479 | assertEq resolved expected; 480 | 481 | workspace-virtual = let 482 | lock = fromTOML (readFile ../tests/workspace-virtual/Cargo.lock); 483 | cargoTomlFoo = fromTOML (readFile ../tests/workspace-virtual/crates/foo/Cargo.toml); 484 | cargoTomlBar = fromTOML (readFile ../tests/workspace-virtual/crates/bar/Cargo.toml); 485 | infoFoo = mkPkgInfoFromCargoToml cargoTomlFoo ""; 486 | infoBar = mkPkgInfoFromCargoToml cargoTomlBar ""; 487 | 488 | getCrateInfo = args: 489 | if args ? source then 490 | throw "No crates.io dependency" 491 | else if args.name == "foo" then 492 | infoFoo 493 | else if args.name == "bar" then 494 | infoBar 495 | else 496 | throw "Unknow crate: ${toJSON args}"; 497 | 498 | resolved = resolveDepsFromLock getCrateInfo lock; 499 | in 500 | assertEq resolved { 501 | bar = { 502 | dependencies = []; 503 | features = {}; 504 | links = null; 505 | name = "bar"; 506 | procMacro = false; 507 | src = ""; 508 | version = "0.1.0"; 509 | }; 510 | foo = { 511 | dependencies = [ { 512 | default_features = true; 513 | features = []; 514 | kind = "normal"; 515 | name = "bar"; 516 | optional = false; 517 | package = "bar"; 518 | req = null; 519 | resolved = "bar"; 520 | source = null; 521 | target = null; 522 | } ]; 523 | features = {}; 524 | links = null; 525 | name = "foo"; 526 | procMacro = false; 527 | src = ""; 528 | version = "0.1.0"; 529 | }; 530 | }; 531 | }; 532 | } 533 | -------------------------------------------------------------------------------- /lib/semver.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) match elemAt fromJSON; 4 | inherit (lib) compare compareLists splitString all any; 5 | in rec { 6 | parseSemver = ver: let 7 | m = match "([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([A-Za-z0-9.-]+))?(\\+[A-Za-z0-9.-]+)?" ver; 8 | in 9 | if m == null then 10 | throw "Invalid semver: `${ver}`" 11 | else { 12 | maj = fromJSON (elemAt m 0); 13 | min = fromJSON (elemAt m 1); 14 | pat = fromJSON (elemAt m 2); 15 | pre = elemAt m 4; 16 | }; 17 | 18 | compareSemver = a: b: let 19 | m1 = parseSemver a; 20 | m2 = parseSemver b; 21 | in 22 | if m1.maj != m2.maj then 23 | if m1.maj < m2.maj then -1 else 1 24 | else if m1.min != m2.min then 25 | if m1.min < m2.min then -1 else 1 26 | else if m1.pat != m2.pat then 27 | if m1.pat < m2.pat then -1 else 1 28 | else 29 | comparePre m1.pre m2.pre; 30 | 31 | comparePre = a: b: 32 | if a == null then 33 | if b == null then 34 | 0 35 | else 36 | 1 37 | else if b == null then 38 | -1 39 | else 40 | comparePreList (splitString "." a) (splitString "." b); 41 | 42 | isNumber = s: match "[0-9]+" s != null; 43 | 44 | comparePreList = compareLists (a: b: 45 | let 46 | num1 = if isNumber a then fromJSON a else null; 47 | num2 = if isNumber b then fromJSON b else null; 48 | in 49 | if num1 != null then 50 | if num2 != null then 51 | compare num1 num2 52 | else 53 | -1 54 | else if num2 != null then 55 | 1 56 | else 57 | compare a b 58 | ); 59 | 60 | parseSemverReq = req: let 61 | reqs = splitString "," req; 62 | comparators = map parseComparators reqs; 63 | in 64 | ver: all (f: f ver) comparators && (isPreVersion ver -> any (containsExactPreVersion ver) reqs); 65 | 66 | isPreVersion = ver: match "[0-9.]+-.*" ver != null; 67 | containsExactPreVersion = ver: req: let 68 | m = parseSemver ver; 69 | in 70 | match " *(=|<|<=|>|>=|~|\\^)? *${toString m.maj}\\.${toString m.min}\\.${toString m.pat}-.*" req != null; 71 | 72 | opEq = { compMaj, compMin, compPat, compPre }: { maj, min, pat, pre }: 73 | maj == compMaj && 74 | (compMin == null || min == compMin) && 75 | (compPat == null || pat == compPat) && 76 | (compPre == null || comparePre pre compPre == 0); 77 | 78 | opLtGt = op: { compMaj, compMin, compPat, compPre }: { maj, min, pat, pre }: 79 | if maj != compMaj then 80 | op maj compMaj 81 | else if compMin == null then 82 | false 83 | else if min != compMin then 84 | op min compMin 85 | else if compPat == null then 86 | false 87 | else if pat != compPat then 88 | op pat compPat 89 | else 90 | op (comparePre pre compPre) 0; 91 | 92 | opTilde = { compMaj, compMin, compPat, compPre }: { maj, min, pat, pre }: 93 | maj == compMaj && 94 | (compMin == null || min == compMin) && 95 | (compPat == null || pat > compPat || (pat == compPat && 96 | comparePre pre compPre >= 0)); 97 | 98 | opCaret = { compMaj, compMin, compPat, compPre }: { maj, min, pat, pre }: 99 | if maj != compMaj then 100 | false 101 | else if compMin == null then 102 | true 103 | else if compPat == null then 104 | if maj > 0 then 105 | min >= compMin 106 | else 107 | min == compMin 108 | else if maj > 0 then 109 | if min != compMin then 110 | min > compMin 111 | else if pat != compPat then 112 | pat > compPat 113 | else 114 | comparePre pre compPre >= 0 115 | else if min > 0 then 116 | if min != compMin then 117 | false 118 | else if pat != compPat then 119 | pat > compPat 120 | else 121 | comparePre pre compPre >= 0 122 | else if min != compMin || pat != compPat then 123 | false 124 | else 125 | comparePre pre compPre >= 0; 126 | 127 | parseComparators = req: let 128 | toInt = s: if s == null then null else fromJSON s; 129 | 130 | star = match " *(([0-9]+)\\.(([0-9]+)\\.)?)?\\*(\\.\\*)? *" req; 131 | star0 = elemAt star 1; 132 | star1 = elemAt star 3; 133 | comp = match " *(=|>|>=|<|<=|~|\\^)? *(([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(-([A-Za-z0-9.-]+))?(\\+[A-Za-z0-9.-]*)?)?)?) *" req; 134 | compOp = elemAt comp 0; 135 | compVer = elemAt comp 1; 136 | compArgs = { 137 | compMaj = toInt (elemAt comp 2); 138 | compMin = toInt (elemAt comp 4); 139 | compPat = toInt (elemAt comp 6); 140 | compPre = elemAt comp 8; 141 | }; 142 | 143 | less = a: b: a < b; 144 | greater = a: b: a > b; 145 | op = { 146 | "=" = opEq compArgs; 147 | "<" = opLtGt less compArgs; 148 | "<=" = args: opLtGt less compArgs args || opEq compArgs args; 149 | ">" = opLtGt greater compArgs; 150 | ">=" = args: opLtGt greater compArgs args || opEq compArgs args; 151 | "~" = opTilde compArgs; 152 | "^" = opCaret compArgs; 153 | }.${if compOp != null then compOp else "^"}; 154 | 155 | in 156 | if star != null then 157 | if star0 == null then 158 | (ver: true) 159 | else if star1 == null then 160 | (ver: match "${toString star0}\\..*" ver != null) 161 | else 162 | (ver: match "${toString star0}\\.${toString star1}\\..*" ver != null) 163 | else if comp != null then 164 | (ver: op (parseSemver ver)) 165 | else 166 | throw "Invalid version comparator: `${req}`"; 167 | 168 | semver-compare-tests = { assertEq, ... }: { 169 | compare-simple1 = assertEq (compareSemver "1.2.3" "1.2.2") 1; 170 | compare-simple2 = assertEq (compareSemver "1.2.3" "1.2.3") 0; 171 | compare-simple3 = assertEq (compareSemver "1.2.3" "1.2.4") (-1); 172 | compare-simple4 = assertEq (compareSemver "1.2.3" "1.1.3") 1; 173 | compare-simple5 = assertEq (compareSemver "1.2.3" "1.3.3") (-1); 174 | compare-simple6 = assertEq (compareSemver "1.2.3" "0.2.3") 1; 175 | compare-simple7 = assertEq (compareSemver "1.2.3" "2.2.3") (-1); 176 | }; 177 | 178 | # From https://github.com/dtolnay/semver/blob/a03d376560e0c4d16518bc271867b1981c85acf0/tests/test_version_req.rs 179 | semver-req-tests = { assertEq, ... }: let 180 | testMatchReq = req: { yes ? [], no ? [] }: let 181 | inherit (lib) const; 182 | checker = parseSemverReq req; 183 | got = map checker (yes ++ no); 184 | expect = map (const true) yes ++ map (const false) no; 185 | in 186 | assertEq got expect; 187 | in { 188 | eq1 = testMatchReq "=1.0.0" { 189 | yes = [ "1.0.0" ]; 190 | no = [ "1.0.1" "0.9.9" "0.10.0" "0.1.0" "1.0.0-pre" ]; 191 | }; 192 | default = testMatchReq "^1.0.0" { 193 | yes = [ "1.0.0" "1.1.0" "1.0.1" ]; 194 | no = [ "0.9.9" "0.10.0" "0.1.0" "1.0.0-pre" "1.0.1-pre" ]; 195 | }; 196 | exact1 = testMatchReq "=1.0.0" { 197 | yes = [ "1.0.0" ]; 198 | no = [ "1.0.1" "0.9.9" "0.10.0" "0.1.0" "1.0.0-pre" ]; 199 | }; 200 | exact2 = testMatchReq "=0.9.0" { 201 | yes = [ "0.9.0" ]; 202 | no = [ "0.9.1" "1.9.0" "0.0.9" "0.9.0-pre" ]; 203 | }; 204 | exact3 = testMatchReq "=0.0.2" { 205 | yes = [ "0.0.2" ]; 206 | no = [ "0.0.1" "0.0.3" "0.0.2-pre" ]; 207 | }; 208 | exact4 = testMatchReq "=0.1.0-beta2.a" { 209 | yes = [ "0.1.0-beta2.a" ]; 210 | no = [ "0.9.1" "0.1.0" "0.1.1-beta2.a" "0.1.0-beta2" ]; 211 | }; 212 | exact5 = testMatchReq "=0.1.0" { 213 | yes = [ "0.1.0" "0.1.0+meta" "0.1.0+any" ]; 214 | }; 215 | gt1 = testMatchReq ">= 1.0.0" { 216 | yes = [ "1.0.0" "2.0.0" ]; 217 | no = [ "0.1.0" "0.0.1" "1.0.0-pre" "2.0.0-pre" ]; 218 | }; 219 | gt2 = testMatchReq ">=2.1.0-alpha2" { 220 | yes = [ "2.1.0-alpha2" "2.1.0-alpha3" "2.1.0" "3.0.0" ]; 221 | no = [ "2.0.0" "2.1.0-alpha1" "2.0.0-alpha2" "3.0.0-alpha2" ]; 222 | }; 223 | lt1 = testMatchReq "<1.0.0" { 224 | yes = [ "0.1.0" "0.0.1" ]; 225 | no = [ "1.0.0" "1.0.0-beta" "1.0.1" "0.9.9-alpha" ]; 226 | }; 227 | le1 = testMatchReq "<= 2.1.0-alpha2" { 228 | yes = [ "2.1.0-alpha2" "2.1.0-alpha1" "2.0.0" "1.0.0" ]; 229 | no = [ "2.1.0" "2.2.0-alpha1" "2.0.0-alpha2" "1.0.0-alpha2" ]; 230 | }; 231 | multi1 = testMatchReq ">1.0.0-alpha, <1.0.0" { 232 | yes = [ "1.0.0-beta" ]; 233 | }; 234 | multi2 = testMatchReq ">1.0.0-alpha, <1.0" { 235 | no = [ "1.0.0-beta" ]; 236 | }; 237 | multi3 = testMatchReq ">1.0.0-alpha, <1" { 238 | no = [ "1.0.0-beta" ]; 239 | }; 240 | multi4 = testMatchReq "> 0.0.9, <= 2.5.3" { 241 | yes = [ "0.0.10" "1.0.0" "2.5.3" ]; 242 | no = [ "0.0.8" "2.5.4" ]; 243 | }; 244 | multi5 = testMatchReq "0.3.0, 0.4.0" { 245 | no = [ "0.0.8" "0.3.0" "0.4.0" ]; 246 | }; 247 | multi6 = testMatchReq "<= 0.2.0, >= 0.5.0" { 248 | no = [ "0.0.8" "0.3.0" "0.5.1" ]; 249 | }; 250 | multi7 = testMatchReq "^0.1.0, ^0.1.4, ^0.1.6" { 251 | yes = [ "0.1.6" "0.1.9" ]; 252 | no = [ "0.1.0" "0.1.4" "0.2.0" ]; 253 | }; 254 | multi8 = testMatchReq ">=0.5.1-alpha3, <0.6" { 255 | yes = [ "0.5.1-alpha3" "0.5.1-alpha4" "0.5.1-beta" "0.5.1" "0.5.5" ]; 256 | no = [ "0.5.1-alpha1" "0.5.2-alpha3" "0.5.5-pre" "0.5.0-pre" "0.6.0" "0.6.0-pre" ]; 257 | }; 258 | tilde1 = testMatchReq "~1" { 259 | yes = [ "1.0.0" "1.0.1" "1.1.1" ]; 260 | no = [ "0.9.1" "2.9.0" "0.0.9" ]; 261 | }; 262 | tilde2 = testMatchReq "~1.2" { 263 | yes = [ "1.2.0" "1.2.1" ]; 264 | no = [ "1.1.1" "1.3.0" "0.0.9" ]; 265 | }; 266 | tilde3 = testMatchReq "~1.2.2" { 267 | yes = [ "1.2.2" "1.2.4" ]; 268 | no = [ "1.2.1" "1.9.0" "1.0.9" "2.0.1" "0.1.3" ]; 269 | }; 270 | tilde4 = testMatchReq "~1.2.3-beta.2" { 271 | yes = [ "1.2.3" "1.2.4" "1.2.3-beta.2" "1.2.3-beta.4" ]; 272 | no = [ "1.3.3" "1.1.4" "1.2.3-beta.1" "1.2.4-beta.2" ]; 273 | }; 274 | caret1 = testMatchReq "^1" { 275 | yes = [ "1.1.2" "1.1.0" "1.2.1" "1.0.1" ]; 276 | no = [ "0.9.1" "2.9.0" "0.1.4" "1.0.0-beta1" "0.1.0-alpha" "1.0.1-pre" ]; 277 | }; 278 | caret2 = testMatchReq "^1.1" { 279 | yes = [ "1.1.2" "1.1.0" "1.2.1" ]; 280 | no = [ "0.9.1" "2.9.0" "1.0.1" "0.1.4" ]; 281 | }; 282 | caret3 = testMatchReq "^1.1.2" { 283 | yes = [ "1.1.2" "1.1.4" "1.2.1" ]; 284 | no = [ "0.9.1" "2.9.0" "1.1.1" "0.0.1" "1.1.2-alpha1" "1.1.3-alpha1" "2.9.0-alpha1" ]; 285 | }; 286 | caret4 = testMatchReq "^0.1.2" { 287 | yes = [ "0.1.2" "0.1.4" ]; 288 | no = [ "0.9.1" "2.9.0" "1.1.1" "0.0.1" "0.1.2-beta" "0.1.3-alpha" "0.2.0-pre" ]; 289 | }; 290 | caret5 = testMatchReq "^0.5.1-alpha3" { 291 | yes = [ "0.5.1-alpha3" "0.5.1-alpha4" "0.5.1-beta" "0.5.1" "0.5.5" ]; 292 | no = [ "0.5.1-alpha1" "0.5.2-alpha3" "0.5.5-pre" "0.5.0-pre" "0.6.0" ]; 293 | }; 294 | caret6 = testMatchReq "^0.0.2" { 295 | yes = [ "0.0.2" ]; 296 | no = [ "0.9.1" "2.9.0" "1.1.1" "0.0.1" "0.1.4" ]; 297 | }; 298 | caret7 = testMatchReq "^0.0" { 299 | yes = [ "0.0.2" "0.0.0" ]; 300 | no = [ "0.9.1" "2.9.0" "1.1.1" "0.1.4" ]; 301 | }; 302 | caret8 = testMatchReq "^0" { 303 | yes = [ "0.9.1" "0.0.2" "0.0.0" ]; 304 | no = [ "2.9.0" "1.1.1" ]; 305 | }; 306 | caret9 = testMatchReq "^1.4.2-beta.5" { 307 | yes = [ "1.4.2" "1.4.3" "1.4.2-beta.5" "1.4.2-beta.6" "1.4.2-c" ]; 308 | no = [ "0.9.9" "2.0.0" "1.4.2-alpha" "1.4.2-beta.4" "1.4.3-beta.5" ]; 309 | }; 310 | star1 = testMatchReq "*" { 311 | yes = [ "0.9.1" "2.9.0" "0.0.9" "1.0.1" "1.1.1" ]; 312 | }; 313 | star2 = testMatchReq "1.*" { 314 | yes = [ "1.2.0" "1.2.1" "1.1.1" "1.3.0" ]; 315 | no = [ "0.0.9" ]; 316 | }; 317 | star3 = testMatchReq "1.2.*" { 318 | yes = [ "1.2.0" "1.2.2" "1.2.4" ]; 319 | no = [ "1.9.0" "1.0.9" "2.0.1" "0.1.3" ]; 320 | }; 321 | pre = testMatchReq "=2.1.1-really.0" { 322 | yes = [ "2.1.1-really.0" ]; 323 | }; 324 | }; 325 | } 326 | -------------------------------------------------------------------------------- /lib/support.nix: -------------------------------------------------------------------------------- 1 | { lib, self }: 2 | let 3 | inherit (builtins) fromTOML toJSON match tryEval split; 4 | inherit (lib) 5 | readFile mapAttrs mapAttrs' makeOverridable warnIf 6 | isString hasPrefix 7 | filter flatten elem elemAt listToAttrs subtractLists concatStringsSep 8 | attrNames attrValues recursiveUpdate optionalAttrs; 9 | inherit (self.pkg-info) mkPkgInfoFromCargoToml getPkgInfoFromIndex toPkgId; 10 | inherit (self.resolve) resolveDepsFromLock resolveFeatures; 11 | inherit (self.target-cfg) platformToCfgs evalTargetCfgStr; 12 | inherit (self.glob) globMatchDir; 13 | in 14 | rec { 15 | 16 | # https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles 17 | defaultProfiles = rec { 18 | dev = { 19 | name = "dev"; 20 | build-override = defaultBuildProfile; 21 | opt-level = 0; 22 | debug = true; 23 | debug-assertions = true; 24 | overflow-checks = true; 25 | lto = false; 26 | panic = "unwind"; 27 | codegen-units = 256; 28 | rpath = false; 29 | }; 30 | release = { 31 | name = "release"; 32 | build-override = defaultBuildProfile; 33 | opt-level = 3; 34 | debug = false; 35 | debug-assertions = false; 36 | overflow-checks = false; 37 | lto = false; 38 | panic = "unwind"; 39 | codegen-units = 16; 40 | rpath = false; 41 | }; 42 | test = dev // { name = "test"; }; 43 | bench = release // { name = "bench"; }; 44 | }; 45 | 46 | defaultBuildProfile = { 47 | opt-level = 0; 48 | codegen-units = 256; 49 | }; 50 | 51 | profilesFromManifest = manifest: 52 | let 53 | knownFields = [ 54 | "name" 55 | "inherits" 56 | # "package" # Unsupported yet. 57 | "build-override" 58 | 59 | "opt-level" 60 | "debug" 61 | # split-debug-info # Unsupported. 62 | "strip" 63 | "debug-assertions" 64 | "overflow-checks" 65 | "lto" 66 | "panic" 67 | # incremental # Unsupported. 68 | "codegen-units" 69 | "rpath" 70 | ]; 71 | 72 | profiles = mapAttrs (name: p: 73 | let unknown = removeAttrs p knownFields; in 74 | warnIf (unknown != {}) "Unsupported fields of profile ${name}: ${toString (attrNames unknown)}" 75 | (optionalAttrs (p ? inherits) profiles.${p.inherits} // p) 76 | ) (recursiveUpdate defaultProfiles (manifest.profile or {})); 77 | 78 | in profiles; 79 | 80 | mkRustPackageOrWorkspace = 81 | { defaultRegistries, pkgsBuildHost, buildRustCrate, stdenv }@default: 82 | { src # : Path 83 | , gitSrcs ? {} # : Attrset Path 84 | , buildCrateOverrides ? {} # : Attrset (Attrset _) 85 | , extraRegistries ? {} # : Attrset Registry 86 | , registries ? defaultRegistries // extraRegistries 87 | 88 | , rustc ? pkgsBuildHost.rustc 89 | , stdenv ? default.stdenv 90 | }: 91 | let 92 | manifest = fromTOML (readFile (src + "/Cargo.toml")); 93 | 94 | profiles = profilesFromManifest manifest; 95 | 96 | selected = flatten (map (glob: globMatchDir glob src) manifest.workspace.members); 97 | excluded = map sanitizeRelativePath (manifest.workspace.exclude or []); 98 | members = subtractLists excluded selected; 99 | 100 | lock = fromTOML (readFile (src + "/Cargo.lock")); 101 | # We don't distinguish between v1 and v2. But v3 is different from both. 102 | lockVersionSet = { lockVersion = lock.version or 2; }; 103 | 104 | localSrcInfos = 105 | listToAttrs 106 | (map (relativePath: 107 | let 108 | memberRoot = src + ("/" + relativePath); 109 | memberManifest = fromTOML (readFile (memberRoot + "/Cargo.toml")) // lockVersionSet; 110 | in { 111 | name = toPkgId memberManifest.package; 112 | value = mkPkgInfoFromCargoToml memberManifest memberRoot; 113 | } 114 | ) (if manifest ? workspace then members else [ "" ])); 115 | 116 | in mkRustPackageSet { 117 | gitSrcInfos = mapAttrs (url: src: 118 | mkPkgInfoFromCargoToml (fromTOML (readFile (src + "/Cargo.toml")) // lockVersionSet) src 119 | ) gitSrcs; 120 | 121 | inherit lock profiles localSrcInfos buildRustCrate buildCrateOverrides registries rustc stdenv; 122 | }; 123 | 124 | # -> { = { = ; }; } 125 | mkRustPackageSet = 126 | { lock # : 127 | , localSrcInfos # : Attrset PkgInfo 128 | , gitSrcInfos # : Attrset PkgInfo 129 | , profiles # : Attrset Profile 130 | , buildCrateOverrides # : Attrset (Attrset _) 131 | , buildRustCrate # : Attrset -> Derivation 132 | , registries # : Attrset Registry 133 | 134 | # FIXME: Cross compilation. 135 | , rustc 136 | , stdenv 137 | }: 138 | let 139 | 140 | getPkgInfo = { source ? null, name, version, ... }@args: let 141 | m = match "(registry|git)\\+([^#]*).*" source; 142 | kind = elemAt m 0; 143 | url = elemAt m 1; 144 | in 145 | # Local crates have no `source`. 146 | if source == null then 147 | localSrcInfos.${toPkgId args} 148 | or (throw "Local crate is outside the workspace: ${toPkgId args}") 149 | // { isLocalPkg = true; } 150 | else if m == null then 151 | throw "Invalid source: ${source}" 152 | else if kind == "registry" then 153 | getPkgInfoFromIndex 154 | (registries.${url} or 155 | (throw "Registry `${url}` not found. Please define it in `extraRegistries`.")) 156 | args 157 | // { inherit source; } # `source` is for crate id, which is used for overrides. 158 | else if kind == "git" then 159 | gitSrcInfos.${url} 160 | or (throw "Git source `${url}` not found. Please define it in `gitSrcs`.") 161 | else 162 | throw "Invalid source: ${source}"; 163 | 164 | hostCfgs = platformToCfgs stdenv.hostPlatform; 165 | 166 | pkgSetRaw = resolveDepsFromLock getPkgInfo lock; 167 | pkgSet = mapAttrs (id: info: info // { 168 | dependencies = map (dep: dep // { 169 | targetEnabled = dep.target != null -> evalTargetCfgStr hostCfgs dep.target; 170 | }) info.dependencies; 171 | }) pkgSetRaw; 172 | 173 | selectDeps = pkgs: deps: features: selectKind: onlyLinks: 174 | map 175 | (dep: { rename = dep.rename or null; drv = pkgs.${dep.resolved}; }) 176 | (filter 177 | ({ kind, name, optional, targetEnabled, resolved, ... }@dep: 178 | targetEnabled && kind == selectKind 179 | && (optional -> elem name features) 180 | && (if resolved == null then throw "Unresolved dependency: ${toJSON dep}" else true) 181 | && (onlyLinks -> pkgSet.${resolved}.links != null)) 182 | deps); 183 | 184 | buildRustCrate' = info: args: 185 | let 186 | # TODO: Proc macro crates should behave differently in dependency resolution. 187 | # But this override is applied just before the `buildRustCrate` call. 188 | args' = args // (info.__override or lib.id) args; 189 | args'' = args' // (buildCrateOverrides.${toPkgId info} or lib.id) args'; 190 | in 191 | buildRustCrate args''; 192 | 193 | mkPkg = profile: rootId: makeOverridable ( 194 | { features }: 195 | let 196 | rootFeatures = if features != null then features 197 | else if pkgSet.${rootId}.features ? default then [ "default" ] 198 | else []; 199 | 200 | resolvedBuildFeatures = resolveFeatures { 201 | inherit pkgSet rootId rootFeatures; 202 | depFilter = dep: dep.targetEnabled && dep.kind == "normal" || dep.kind == "build"; 203 | }; 204 | resolvedNormalFeatures = resolveFeatures { 205 | inherit pkgSet rootId rootFeatures; 206 | depFilter = dep: dep.targetEnabled && dep.kind == "normal"; 207 | }; 208 | 209 | pkgsBuild = mapAttrs (id: features: let info = pkgSet.${id}; in 210 | if features != null then 211 | buildRustCrate' info { 212 | inherit (info) version src procMacro; 213 | inherit features profile rustc; 214 | pname = info.name; 215 | capLints = if localSrcInfos ? id then null else "allow"; 216 | buildDependencies = selectDeps pkgsBuild info.dependencies features "build" false; 217 | # Build dependency's normal dependency is still build dependency. 218 | dependencies = selectDeps pkgsBuild info.dependencies features "normal" false; 219 | linksDependencies = selectDeps pkgsBuild info.dependencies features "normal" true; 220 | } 221 | else 222 | null 223 | ) resolvedBuildFeatures; 224 | 225 | pkgs = mapAttrs (id: features: let info = pkgSet.${id}; in 226 | if features != null then 227 | buildRustCrate' info { 228 | inherit (info) version src links procMacro; 229 | inherit features profile rustc; 230 | pname = info.name; 231 | capLints = if localSrcInfos ? id then null else "allow"; 232 | buildDependencies = selectDeps pkgsBuild info.dependencies features "build" false; 233 | dependencies = selectDeps pkgs info.dependencies features "normal" false; 234 | linksDependencies = selectDeps pkgs info.dependencies features "normal" true; 235 | } 236 | else 237 | null 238 | ) resolvedNormalFeatures; 239 | in 240 | pkgs.${rootId} 241 | ) { 242 | features = null; 243 | }; 244 | 245 | in 246 | mapAttrs (_: profile: 247 | mapAttrs' (pkgId: pkgInfo: { 248 | name = pkgInfo.name; 249 | value = mkPkg profile pkgId; 250 | }) localSrcInfos 251 | ) profiles; 252 | 253 | sanitizeRelativePath = path: 254 | if hasPrefix "/" path then 255 | throw "Absolute path is not allowed: ${path}" 256 | else 257 | concatStringsSep "/" 258 | (filter 259 | (s: 260 | isString s && s != "" && s != "." && 261 | (if match ''.*[[?*].*|\.\.'' s != null then throw '' 262 | Globing and `..` are not allowed: ${path} 263 | '' else true)) 264 | (split ''[\/]'' path)); 265 | 266 | build-from-src-dry-tests = { assertEq, pkgs, defaultRegistries, ... }: let 267 | inherit (builtins) head listToAttrs; 268 | 269 | mkPackage = pkgs.callPackage mkRustPackageOrWorkspace { 270 | inherit defaultRegistries; 271 | buildRustCrate = args: args; 272 | }; 273 | 274 | build = src: args: 275 | head (attrValues (mkPackage ({ inherit src; } // args)).dev); 276 | 277 | in 278 | { 279 | features = let ret = build ../tests/features {}; in 280 | assertEq ret.features [ "a" "default" "semver" ]; # FIXME 281 | 282 | dependency-features = let 283 | ret = build ../tests/features { }; 284 | semver = (head ret.dependencies).drv; 285 | serde = (head semver.dependencies).drv; 286 | in assertEq 287 | [ semver.features serde.features ] 288 | [ [ "default" "serde" "std" ] [ /* Don't trigger default features */ ] ]; 289 | 290 | dependency-overrided = let 291 | ret = build ../tests/features { 292 | buildCrateOverrides."" = old: { a = "b"; }; 293 | buildCrateOverrides."serde 1.0.139 (registry+https://github.com/rust-lang/crates.io-index)" = old: { 294 | buildInputs = [ "some-inputs" ]; 295 | }; 296 | }; 297 | semver = (head ret.dependencies).drv; 298 | serde = (head semver.dependencies).drv; 299 | in 300 | assertEq serde.buildInputs [ "some-inputs" ]; 301 | 302 | dependency-kinds = let 303 | mkSrc = from: { __toString = _: ../tests/fake-semver; inherit from; }; 304 | gitSrcs = { 305 | "https://github.com/dtolnay/semver?tag=1.0.0" = mkSrc "tag"; 306 | "http://github.com/dtolnay/semver" = mkSrc "branch"; # v1, v2 307 | "http://github.com/dtolnay/semver?branch=master" = mkSrc "branch"; # v3 308 | "ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" = mkSrc "rev"; 309 | "ssh://git@github.com/dtolnay/semver" = mkSrc "nothing"; 310 | }; 311 | extraRegistries = { 312 | "https://www.github.com/rust-lang/crates.io-index" = 313 | head (attrValues defaultRegistries); 314 | }; 315 | 316 | ret = build ../tests/dependency-v3 { 317 | inherit gitSrcs extraRegistries; 318 | }; 319 | ret' = listToAttrs 320 | (map (dep: { 321 | name = if dep.rename != null then dep.rename else dep.drv.pname; 322 | value = dep.drv.src.from or dep.drv.src.name; 323 | }) ret.dependencies); 324 | in 325 | assertEq ret' { 326 | cratesio = "crate-semver-1.0.12.tar.gz"; 327 | git_branch = "branch"; 328 | git_head = "nothing"; 329 | git_rev = "rev"; 330 | git_tag = "tag"; 331 | registry_index = "crate-semver-1.0.12.tar.gz"; 332 | }; 333 | 334 | libz-propagated = let 335 | ret = build ../tests/libz-dynamic {}; 336 | libz = (head ret.dependencies).drv; 337 | in 338 | assertEq (head libz.propagatedBuildInputs).pname "zlib"; 339 | 340 | libz-link = let 341 | ret = build ../tests/libz-dynamic {}; 342 | libz = (head ret.dependencies).drv; 343 | libz' = (head ret.linksDependencies).drv; 344 | in 345 | assertEq [ libz.links libz'.links ] [ "z" "z" ]; 346 | }; 347 | 348 | sanitize-relative-path-tests = { assertEq, ... }: let 349 | assertOk = raw: expect: assertEq (tryEval (sanitizeRelativePath raw)) { success = true; value = expect; }; 350 | assertInvalid = raw: assertEq (tryEval (sanitizeRelativePath raw)) { success = false; value = false; }; 351 | in 352 | { 353 | empty = assertOk "" ""; 354 | simple1 = assertOk "foo" "foo"; 355 | simple2 = assertOk "foo/bar" "foo/bar"; 356 | dot1 = assertOk "." ""; 357 | dot2 = assertOk "./././" ""; 358 | dot3 = assertOk "./foo/./bar/" "foo/bar"; 359 | 360 | dotdot1 = assertInvalid ".."; 361 | dotdot2 = assertInvalid "./foo/.."; 362 | dotdot3 = assertInvalid "../bar"; 363 | root1 = assertInvalid "/"; 364 | root2 = assertInvalid "/foo"; 365 | }; 366 | } 367 | -------------------------------------------------------------------------------- /lib/target-cfg.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (builtins) match tryEval; 4 | inherit (lib) 5 | concatStrings 6 | length elem elemAt any all sort flatten isList 7 | optionalAttrs mapAttrsToList; 8 | in 9 | rec { 10 | # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch 11 | platformToTargetArch = platform: 12 | if platform.isAarch32 then "arm" 13 | else platform.parsed.cpu.name; 14 | 15 | # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os 16 | platformToTargetOs = platform: 17 | if platform.isDarwin then "macos" 18 | else platform.parsed.kernel.name; 19 | 20 | # https://github.com/rust-lang/rust/blob/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/compiler/rustc_session/src/config.rs#L835 21 | # https://doc.rust-lang.org/reference/conditional-compilation.html 22 | platformToCfgAttrs = platform: { 23 | # Arch info. 24 | # https://github.com/NixOS/nixpkgs/blob/c63d4270feed5eb6c578fe2d9398d3f6f2f96811/pkgs/build-support/rust/build-rust-crate/configure-crate.nix#L126 25 | target_arch = platformToTargetArch platform; 26 | target_endian = if platform.isLittleEndian then "little" 27 | else if platform.isBigEndian then "big" 28 | else throw "Unknow target_endian for ${platform.config}"; 29 | target_env = if platform.isNone then "" 30 | else if platform.libc == "glibc" then "gnu" 31 | else if platform.isMusl then "musl" 32 | else if platform.isDarwin then "" # Empty 33 | else lib.trace platform (throw "Unknow target_env for ${platform.config}"); 34 | target_family = if platform.isUnix then "unix" 35 | else if platform.isWindows then "windows" 36 | else null; 37 | target_os = platformToTargetOs platform; 38 | target_pointer_width = toString platform.parsed.cpu.bits; 39 | target_vendor = platform.parsed.vendor.name; 40 | } // optionalAttrs platform.isx86 { 41 | # These features are assume to be available. 42 | target_feature = [ "fxsr" "sse" "sse2" ]; 43 | } // optionalAttrs platform.isUnix { 44 | unix = true; 45 | } // optionalAttrs platform.isWindows { 46 | windows = true; 47 | }; 48 | 49 | platformToCfgs = platform: 50 | flatten ( 51 | mapAttrsToList (key: value: 52 | if value == true then { inherit key; } 53 | else if isList value then map (value: { inherit key value; }) value 54 | else { inherit key value; } 55 | ) (platformToCfgAttrs platform)); 56 | 57 | # cfgs: [ 58 | # { key = "atom1"; } 59 | # { key = "atom2"; } 60 | # { key = "feature"; value = "foo"; } 61 | # { key = "feature"; value = "bar"; } 62 | # ] 63 | evalTargetCfgStr = cfgs: s: 64 | evalCfgExpr cfgs (parseTargetCfgExpr s); 65 | 66 | # Cargo's parse is stricter than rustc's. 67 | # - Must starts with `cfg(` and ends with `)`. No spaces are allowed before and after. 68 | # - Identifiers must follows /[A-Za-z_][A-Za-z_0-9]*/. 69 | # - Raw identifiers, raw strings, escapes in strings are not allowed. 70 | # 71 | # The target can also be a simple target name like `aarch64-unknown-linux-gnu`, which will be parsed 72 | # as if it's `cfg(target = "...")`. 73 | # 74 | # https://github.com/rust-lang/cargo/blob/dcc95871605785c2c1f2279a25c6d3740301c468/crates/cargo-platform/src/cfg.rs 75 | parseTargetCfgExpr = cfg: let 76 | fail = reason: throw "${reason}, when parsing `${cfg}"; 77 | 78 | go = { fn, values, afterComma, prev }@stack: s: let 79 | m = match ''((all|any|not) *\( *|(\)) *|(,) *|([A-Za-z_][A-Za-z_0-9]*) *(= *"([^"]*)" *)?)(.*)'' s; 80 | mFn = elemAt m 1; 81 | mClose = elemAt m 2; 82 | mComma = elemAt m 3; 83 | mIdent = elemAt m 4; 84 | mString = elemAt m 6; 85 | mRest = elemAt m 7; 86 | in 87 | if s == "" then 88 | stack 89 | else if m == null then 90 | fail "No parse `${s}`" 91 | # else if builtins.trace ([ stack m ]) (mFn != null) then 92 | else if mFn != null then 93 | if !afterComma then 94 | fail "Missing comma before `${mFn}` at `${s}" 95 | else 96 | go { fn = mFn; values = []; afterComma = true; prev = stack; } mRest 97 | else if mClose != null then 98 | if prev == null then 99 | fail "Unexpected `)` at `${s}`" 100 | else if fn == "not" && length values == 0 then 101 | fail "`not` must have exact one argument, got 0" 102 | else if prev.fn == "not" && length prev.values != 0 then 103 | fail "`not` must have exact one argument, got at least 2" 104 | else 105 | go (prev // { values = prev.values ++ [ { inherit (stack) fn values; } ]; afterComma = false; }) mRest 106 | else if mComma != null then 107 | if afterComma then 108 | fail "Unexpected `,` at `${s}`" 109 | else 110 | go (stack // { afterComma = true; }) mRest 111 | else 112 | if !afterComma then 113 | fail "Missing comma before identifier `${mIdent}` at `${s}" 114 | else if fn == "not" && length values != 0 then 115 | fail "`not` must have exact one argument, got at least 2" 116 | else 117 | let kv = 118 | if mString != null then { key = mIdent; value = mString; } 119 | else { key = mIdent; }; 120 | in 121 | go (stack // { afterComma = false; values = values ++ [ kv ]; }) mRest; 122 | 123 | mSimpleTarget = match "[A-Za-z_0-9_.-]+" cfg; 124 | 125 | mCfg = match ''cfg\( *(.*)\)'' cfg; 126 | mCfgInner = elemAt mCfg 0; 127 | ret = go { fn = "cfg"; values = []; afterComma = true; prev = null; } mCfgInner; 128 | 129 | in 130 | if mSimpleTarget != null then 131 | { key = "target"; value = cfg; } 132 | else if mCfg == null then 133 | fail "Cfg expr must be a simple target string, or start with `cfg(` and end with `)`" 134 | else if ret.prev != null then 135 | fail "Missing `)`" 136 | else if length ret.values != 1 then 137 | fail "`cfg` must have exact one argument, got ${toString (length ret.values)}" 138 | else 139 | elemAt ret.values 0; 140 | 141 | evalCfgExpr = cfgs: tree: 142 | if !(tree ? fn) then 143 | elem tree cfgs 144 | else if tree.fn == "all" then 145 | all (evalCfgExpr cfgs) tree.values 146 | else if tree.fn == "any" then 147 | any (evalCfgExpr cfgs) tree.values 148 | else 149 | !evalCfgExpr cfgs (elemAt tree.values 0); 150 | 151 | cfg-parser-tests = { assertEq, ... }: let 152 | shouldParse = cfg: expect: 153 | assertEq (tryEval (parseTargetCfgExpr cfg)) { success = true; value = expect; }; 154 | shouldNotParse = cfg: 155 | assertEq (tryEval (parseTargetCfgExpr cfg)) { success = false; value = false; }; 156 | in { 157 | simple-target1 = shouldParse "thumbv8m.base-none-eabi" 158 | { key = "target"; value = "thumbv8m.base-none-eabi"; }; 159 | simple-target2 = shouldParse "aarch64-unknown-linux-gnu" 160 | { key = "target"; value = "aarch64-unknown-linux-gnu"; }; 161 | 162 | simple1 = shouldParse "cfg(atom)" { key = "atom"; }; 163 | simple2 = shouldParse ''cfg(k = "v")'' { key = "k"; value = "v"; }; 164 | complex = shouldParse ''cfg( all ( not ( a , ) , b , all ( ) , any ( c , d = "e" ) , ) )'' 165 | { 166 | fn = "all"; 167 | values = [ 168 | { 169 | fn = "not"; 170 | values = [ { key = "a"; } ]; 171 | } 172 | { key = "b"; } 173 | { 174 | fn = "all"; 175 | values = []; 176 | } 177 | { 178 | fn = "any"; 179 | values = [ 180 | { key = "c"; } 181 | { key = "d"; value = "e"; } 182 | ]; 183 | } 184 | ]; 185 | }; 186 | 187 | invalid-cfg1 = shouldNotParse "cfg (a)"; 188 | invalid-cfg2 = shouldNotParse "cfg()"; 189 | invalid-cfg3 = shouldNotParse "cfg(a,b)"; 190 | invalid-not1 = shouldNotParse "cfg(not(a,b))"; 191 | invalid-not2 = shouldNotParse "cfg(not())"; 192 | invalid-comma1 = shouldNotParse "cfg(all(,))"; 193 | invalid-comma2 = shouldNotParse "cfg(all(a,,b))"; 194 | invalid-comma3 = shouldNotParse "cfg(all(a,b,,))"; 195 | invalid-comma4 = shouldNotParse "cfg(all(a b))"; 196 | invalid-comma5 = shouldNotParse "cfg(all(any() any()))"; 197 | invalid-paren1 = shouldNotParse "cfg(all(a)))"; 198 | invalid-paren2 = shouldNotParse "cfg(all(a)"; 199 | }; 200 | 201 | cfg-eval-tests = { assertEq, ... }: let 202 | cfgs = [ 203 | { key = "foo"; } 204 | { key = "bar"; } 205 | { key = "feature"; value = "foo"; } 206 | { key = "feature"; value = "bar"; } 207 | ]; 208 | test = cfg: expect: assertEq (evalTargetCfgStr cfgs cfg) expect; 209 | in { 210 | simple1 = test ''cfg(foo)'' true; 211 | simple2 = test ''cfg(baz)'' false; 212 | simple3 = test ''cfg(feature = "foo")'' true; 213 | simple4 = test ''cfg(foo = "")'' false; 214 | simple5 = test ''cfg(wtf = "foo")'' false; 215 | 216 | all1 = test ''cfg(all())'' true; 217 | all2 = test ''cfg(all(foo))'' true; 218 | all3 = test ''cfg(all(baz))'' false; 219 | all4 = test ''cfg(all(foo,bar))'' true; 220 | all5 = test ''cfg(all(foo,bar,baz))'' false; 221 | all6 = test ''cfg(all(foo,baz,bar))'' false; 222 | all7 = test ''cfg(all(baz,foo))'' false; 223 | all8 = test ''cfg(all(baz,feature="foo"))'' false; 224 | all9 = test ''cfg(all(baz,feature="wtf"))'' false; 225 | all10 = test ''cfg(all(foo,feature="foo"))'' true; 226 | 227 | any1 = test ''cfg(any())'' false; 228 | any2 = test ''cfg(any(foo))'' true; 229 | any3 = test ''cfg(any(baz))'' false; 230 | any4 = test ''cfg(any(foo,bar))'' true; 231 | any5 = test ''cfg(any(foo,bar,baz))'' true; 232 | any6 = test ''cfg(any(foo,baz,bar))'' true; 233 | any7 = test ''cfg(any(baz,foo))'' true; 234 | any8 = test ''cfg(any(baz,feature="foo"))'' true; 235 | any9 = test ''cfg(any(baz,feature="wtf"))'' false; 236 | any10 = test ''cfg(any(foo,feature="wtf"))'' true; 237 | 238 | not1 = test ''cfg(not(foo))'' false; 239 | not2 = test ''cfg(not(wtf))'' true; 240 | }; 241 | 242 | platform-cfg-tests = { assertEq, ... }: let 243 | inherit (lib.systems) elaborate; 244 | test = config: expect: let 245 | cfgs = platformToCfgs (elaborate config); 246 | strs = map ({ key, value ? null }: 247 | if value != null then "${key}=\"${value}\"\n" else "${key}\n" 248 | ) cfgs; 249 | got = concatStrings (sort (a: b: a < b) strs); 250 | in 251 | assertEq got expect; 252 | 253 | in { 254 | attrs-x86_64-linux = assertEq (platformToCfgAttrs (elaborate "x86_64-unknown-linux-gnu")) { 255 | target_arch = "x86_64"; 256 | target_endian = "little"; 257 | target_env = "gnu"; 258 | target_family = "unix"; 259 | target_feature = ["fxsr" "sse" "sse2"]; 260 | target_os = "linux"; 261 | target_pointer_width = "64"; 262 | target_vendor = "unknown"; 263 | unix = true; 264 | }; 265 | 266 | cfg-x86_64-linux = test "x86_64-unknown-linux-gnu" '' 267 | target_arch="x86_64" 268 | target_endian="little" 269 | target_env="gnu" 270 | target_family="unix" 271 | target_feature="fxsr" 272 | target_feature="sse" 273 | target_feature="sse2" 274 | target_os="linux" 275 | target_pointer_width="64" 276 | target_vendor="unknown" 277 | unix 278 | ''; 279 | 280 | cfg-aarch64-linux = test "aarch64-unknown-linux-gnu" '' 281 | target_arch="aarch64" 282 | target_endian="little" 283 | target_env="gnu" 284 | target_family="unix" 285 | target_os="linux" 286 | target_pointer_width="64" 287 | target_vendor="unknown" 288 | unix 289 | ''; 290 | }; 291 | } 292 | -------------------------------------------------------------------------------- /noc/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.63" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a" 19 | 20 | [[package]] 21 | name = "askama" 22 | version = "0.11.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" 25 | dependencies = [ 26 | "askama_derive", 27 | "askama_escape", 28 | "askama_shared", 29 | ] 30 | 31 | [[package]] 32 | name = "askama_derive" 33 | version = "0.11.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" 36 | dependencies = [ 37 | "askama_shared", 38 | "proc-macro2", 39 | "syn", 40 | ] 41 | 42 | [[package]] 43 | name = "askama_escape" 44 | version = "0.10.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" 47 | 48 | [[package]] 49 | name = "askama_shared" 50 | version = "0.12.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" 53 | dependencies = [ 54 | "askama_escape", 55 | "mime", 56 | "mime_guess", 57 | "nom", 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "atty" 65 | version = "0.2.14" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 68 | dependencies = [ 69 | "hermit-abi", 70 | "libc", 71 | "winapi", 72 | ] 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 79 | 80 | [[package]] 81 | name = "bitflags" 82 | version = "1.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 85 | 86 | [[package]] 87 | name = "cargo_toml" 88 | version = "0.11.5" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "5809dd3e6444651fd1cdd3dbec71eca438c439a0fcc8081674a14da0afe50185" 91 | dependencies = [ 92 | "serde", 93 | "serde_derive", 94 | "toml", 95 | ] 96 | 97 | [[package]] 98 | name = "clap" 99 | version = "3.2.20" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" 102 | dependencies = [ 103 | "atty", 104 | "bitflags", 105 | "clap_derive", 106 | "clap_lex", 107 | "indexmap", 108 | "once_cell", 109 | "strsim", 110 | "termcolor", 111 | "textwrap", 112 | ] 113 | 114 | [[package]] 115 | name = "clap_derive" 116 | version = "3.2.18" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" 119 | dependencies = [ 120 | "heck", 121 | "proc-macro-error", 122 | "proc-macro2", 123 | "quote", 124 | "syn", 125 | ] 126 | 127 | [[package]] 128 | name = "clap_lex" 129 | version = "0.2.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 132 | dependencies = [ 133 | "os_str_bytes", 134 | ] 135 | 136 | [[package]] 137 | name = "dissimilar" 138 | version = "1.0.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" 141 | 142 | [[package]] 143 | name = "expect-test" 144 | version = "1.4.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "1d4661aca38d826eb7c72fe128e4238220616de4c0cc00db7bfc38e2e1364dd3" 147 | dependencies = [ 148 | "dissimilar", 149 | "once_cell", 150 | ] 151 | 152 | [[package]] 153 | name = "glob" 154 | version = "0.3.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 157 | 158 | [[package]] 159 | name = "hashbrown" 160 | version = "0.12.3" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 163 | 164 | [[package]] 165 | name = "heck" 166 | version = "0.4.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 169 | 170 | [[package]] 171 | name = "hermit-abi" 172 | version = "0.1.19" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 175 | dependencies = [ 176 | "libc", 177 | ] 178 | 179 | [[package]] 180 | name = "indexmap" 181 | version = "1.9.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 184 | dependencies = [ 185 | "autocfg", 186 | "hashbrown", 187 | ] 188 | 189 | [[package]] 190 | name = "libc" 191 | version = "0.2.132" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 194 | 195 | [[package]] 196 | name = "memchr" 197 | version = "2.5.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 200 | 201 | [[package]] 202 | name = "mime" 203 | version = "0.3.16" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 206 | 207 | [[package]] 208 | name = "mime_guess" 209 | version = "2.0.4" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 212 | dependencies = [ 213 | "mime", 214 | "unicase", 215 | ] 216 | 217 | [[package]] 218 | name = "minimal-lexical" 219 | version = "0.2.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 222 | 223 | [[package]] 224 | name = "nocargo" 225 | version = "0.0.0" 226 | dependencies = [ 227 | "anyhow", 228 | "askama", 229 | "cargo_toml", 230 | "clap", 231 | "expect-test", 232 | "glob", 233 | "once_cell", 234 | "regex", 235 | "toml", 236 | ] 237 | 238 | [[package]] 239 | name = "nom" 240 | version = "7.1.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 243 | dependencies = [ 244 | "memchr", 245 | "minimal-lexical", 246 | ] 247 | 248 | [[package]] 249 | name = "once_cell" 250 | version = "1.14.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" 253 | 254 | [[package]] 255 | name = "os_str_bytes" 256 | version = "6.3.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 259 | 260 | [[package]] 261 | name = "proc-macro-error" 262 | version = "1.0.4" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 265 | dependencies = [ 266 | "proc-macro-error-attr", 267 | "proc-macro2", 268 | "quote", 269 | "syn", 270 | "version_check", 271 | ] 272 | 273 | [[package]] 274 | name = "proc-macro-error-attr" 275 | version = "1.0.4" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 278 | dependencies = [ 279 | "proc-macro2", 280 | "quote", 281 | "version_check", 282 | ] 283 | 284 | [[package]] 285 | name = "proc-macro2" 286 | version = "1.0.43" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 289 | dependencies = [ 290 | "unicode-ident", 291 | ] 292 | 293 | [[package]] 294 | name = "quote" 295 | version = "1.0.21" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 298 | dependencies = [ 299 | "proc-macro2", 300 | ] 301 | 302 | [[package]] 303 | name = "regex" 304 | version = "1.6.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 307 | dependencies = [ 308 | "aho-corasick", 309 | "memchr", 310 | "regex-syntax", 311 | ] 312 | 313 | [[package]] 314 | name = "regex-syntax" 315 | version = "0.6.27" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 318 | 319 | [[package]] 320 | name = "serde" 321 | version = "1.0.144" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" 324 | 325 | [[package]] 326 | name = "serde_derive" 327 | version = "1.0.144" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" 330 | dependencies = [ 331 | "proc-macro2", 332 | "quote", 333 | "syn", 334 | ] 335 | 336 | [[package]] 337 | name = "strsim" 338 | version = "0.10.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 341 | 342 | [[package]] 343 | name = "syn" 344 | version = "1.0.99" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 347 | dependencies = [ 348 | "proc-macro2", 349 | "quote", 350 | "unicode-ident", 351 | ] 352 | 353 | [[package]] 354 | name = "termcolor" 355 | version = "1.1.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 358 | dependencies = [ 359 | "winapi-util", 360 | ] 361 | 362 | [[package]] 363 | name = "textwrap" 364 | version = "0.15.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 367 | 368 | [[package]] 369 | name = "toml" 370 | version = "0.5.9" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 373 | dependencies = [ 374 | "serde", 375 | ] 376 | 377 | [[package]] 378 | name = "unicase" 379 | version = "2.6.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 382 | dependencies = [ 383 | "version_check", 384 | ] 385 | 386 | [[package]] 387 | name = "unicode-ident" 388 | version = "1.0.3" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 391 | 392 | [[package]] 393 | name = "version_check" 394 | version = "0.9.4" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 397 | 398 | [[package]] 399 | name = "winapi" 400 | version = "0.3.9" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 403 | dependencies = [ 404 | "winapi-i686-pc-windows-gnu", 405 | "winapi-x86_64-pc-windows-gnu", 406 | ] 407 | 408 | [[package]] 409 | name = "winapi-i686-pc-windows-gnu" 410 | version = "0.4.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 413 | 414 | [[package]] 415 | name = "winapi-util" 416 | version = "0.1.5" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 419 | dependencies = [ 420 | "winapi", 421 | ] 422 | 423 | [[package]] 424 | name = "winapi-x86_64-pc-windows-gnu" 425 | version = "0.4.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 428 | -------------------------------------------------------------------------------- /noc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nocargo" 3 | version = "0.0.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Helper program for github.com/oxalica/nocargo" 7 | repository = "https://github.com/oxalica/nocargo" 8 | 9 | [[bin]] 10 | name = "noc" 11 | path = "src/main.rs" 12 | 13 | [dependencies] 14 | anyhow = "1.0.40" 15 | askama = { version = "0.11.1", default-features = false } 16 | cargo_toml = "0.11.5" 17 | clap = { version = "3.2.8", features = ["derive"] } 18 | glob = "0.3.0" 19 | once_cell = "1.12.0" 20 | regex = "1.5.4" 21 | toml = "0.5.9" 22 | 23 | [dev-dependencies] 24 | expect-test = "1.3.0" 25 | -------------------------------------------------------------------------------- /noc/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | mod init; 5 | 6 | trait App { 7 | fn run(self) -> Result<()>; 8 | } 9 | 10 | #[derive(Parser)] 11 | #[clap(version, about, long_about = None)] 12 | enum Args { 13 | Init(init::Args), 14 | } 15 | 16 | impl App for Args { 17 | fn run(self) -> Result<()> { 18 | match self { 19 | Self::Init(args) => args.run(), 20 | } 21 | } 22 | } 23 | 24 | fn main() -> Result<()> { 25 | Args::from_args().run() 26 | } 27 | -------------------------------------------------------------------------------- /noc/templates/init-flake.nix: -------------------------------------------------------------------------------- 1 | # See more usages of nocargo at https://github.com/oxalica/nocargo#readme 2 | { 3 | {%- if let Some((pkg_name, _)) = main_pkg %} 4 | description = "Rust package {{ pkg_name|nix_escape }}"; 5 | {%- else %} 6 | description = "My Rust packages"; 7 | {%- endif %} 8 | 9 | inputs = { 10 | nixpkgs.url = "github:NixOS/nixpkgs"; 11 | flake-utils.url = "github:numtide/flake-utils"; 12 | nocargo = { 13 | url = "github:oxalica/nocargo"; 14 | inputs.nixpkgs.follows = "nixpkgs"; 15 | # inputs.registry-crates-io.follows = "registry-crates-io"; 16 | }; 17 | # Optionally, you can override crates.io index to get cutting-edge packages. 18 | # registry-crates-io = { url = "github:rust-lang/crates.io-index"; flake = false; }; 19 | {%- for (_, flake_ref) in registries %} 20 | registry-{{ loop.index }} = { url = "{{ flake_ref|nix_escape }}"; flake = false; }; 21 | {%- endfor %} 22 | {%- for (_, flake_ref) in git_srcs %} 23 | git-{{ loop.index }} = { url = "{{ flake_ref|nix_escape }}"; flake = false; }; 24 | {%- endfor %} 25 | }; 26 | 27 | outputs = { nixpkgs, flake-utils, nocargo, ... }@inputs: 28 | flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: 29 | let 30 | ws = nocargo.lib.${system}.mkRustPackageOrWorkspace { 31 | src = ./.; 32 | {%- if !registries.is_empty() %} 33 | 34 | # Referenced external registries other than crates.io. 35 | extraRegistries = { 36 | {%- for (source_id, _) in registries %} 37 | "{{ source_id|nix_escape }}" = nocargo.lib.${system}.mkIndex inputs.registry-{{ loop.index }} {}; 38 | {%- endfor %} 39 | }; 40 | {%- endif %} 41 | {%- if !git_srcs.is_empty() %} 42 | 43 | # Referenced external rust packages from git. 44 | gitSrcs = { 45 | {%- for (source_id, _) in git_srcs %} 46 | "{{ source_id|nix_escape }}" = inputs.git-{{ loop.index }}; 47 | {%- endfor %} 48 | }; 49 | {%- endif %} 50 | }; 51 | in rec { 52 | {%- if is_workspace %} 53 | packages = {% if let Some((pkg_name, prod)) = main_pkg %}{ 54 | default = packages.{{ pkg_name|ident_or_str }}{% if prod.binary %}.bin{% endif %}; 55 | } // {% endif %}ws.release 56 | // nixpkgs.lib.mapAttrs' (name: value: { name = "${name}-dev"; inherit value; }) ws.dev; 57 | {%- else if let Some((pkg_name, prod)) = main_pkg %} 58 | packages = { 59 | default = packages.{{ pkg_name|ident_or_str }}; 60 | {{ pkg_name|ident_or_str }} = ws.release.{{ pkg_name|ident_or_str }}{% if prod.binary %}.bin{% endif %}; 61 | {{ pkg_name|ident_or_str }}-dev = ws.dev.{{ pkg_name|ident_or_str }}{% if prod.binary %}.bin{% endif %}; 62 | }; 63 | {%- endif %} 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /scripts/cratesio-utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i python3 -p cargo "python3.withPackages (ps: with ps; [ aiohttp toml ])" 3 | from io import BytesIO 4 | from pathlib import Path 5 | from typing import Callable, Optional 6 | from typing_extensions import Self 7 | from urllib import request 8 | import aiohttp 9 | import argparse 10 | import asyncio 11 | import csv 12 | import os 13 | import re 14 | import sqlite3 15 | import subprocess 16 | import sys 17 | import tarfile 18 | import toml 19 | 20 | # Check dependencies. 21 | subprocess.check_call(['cargo', '--version'], stdout=subprocess.DEVNULL) 22 | 23 | # CRATE_TARBALL_URL = 'https://crates.io/api/v1/crates/{name}/{version}/download' # -> 302 24 | CRATE_TARBALL_URL = 'https://static.crates.io/crates/{name}/{name}-{version}.crate' # -> 200 application/gzip 25 | 26 | POPULAR_CRATES_MANIFEST_PATH = Path(__file__).parent.parent / 'cache' / 'Cargo.toml' 27 | PROC_MACRO_LIST_PATH = Path(__file__).parent.parent / 'crates-io-override' / 'proc-macro.nix' 28 | 29 | CACHE_DIR = Path(os.environ.get('XDG_CACHE_HOME') or (Path.home() / '.cache')) / 'cratesio' 30 | CRATES_TOML_DIR = Path(os.environ.get('CRATES_TOML_DIR') or (CACHE_DIR / 'toml')) 31 | 32 | def noisily(*args) -> None: 33 | print(*args, file=sys.stderr) 34 | 35 | class CratesioDB(sqlite3.Connection): 36 | DB_URL = 'https://static.crates.io/db-dump.tar.gz' 37 | 38 | DB_DIR = CACHE_DIR 39 | DB_PATH = DB_DIR / 'db.sqlite' 40 | MTIME_PATH = DB_DIR / 'mtime.txt' 41 | 42 | INIT_SQL = r''' 43 | PRAGMA journal_mode = off; 44 | PRAGMA cache_size = -{cache_kb}; 45 | 46 | CREATE TABLE IF NOT EXISTS crates ( 47 | id INTEGER NOT NULL PRIMARY KEY, 48 | name TEXT NOT NULL, 49 | created_at TEXT NOT NULL, 50 | updated_at TEXT NOT NULL, 51 | downloads INTEGER NOT NULL 52 | ) STRICT; 53 | CREATE TABLE IF NOT EXISTS versions ( 54 | id INTEGER NOT NULL PRIMARY KEY, 55 | crate_id INTEGER NOT NULL, 56 | num TEXT NOT NULL, 57 | updated_at TEXT NOT NULL, 58 | created_at TEXT NOT NULL, 59 | downloads INTEGER NOT NULL, 60 | features TEXT NOT NULL, 61 | yanked INTEGER NOT NULL, 62 | license TEXT 63 | ) STRICT; 64 | CREATE TABLE IF NOT EXISTS version_downloads ( 65 | version_id INTEGER NOT NULL, 66 | downloads INTEGER NOT NULL, 67 | date TEXT NOT NULL, 68 | PRIMARY KEY (version_id, date) 69 | ) STRICT, WITHOUT ROWID; 70 | ''' 71 | 72 | INIT_INDEX_SQL = r''' 73 | CREATE INDEX IF NOT EXISTS versions_ix_crate_to_version ON versions 74 | (crate_id, id); 75 | ANALYZE; 76 | ''' 77 | 78 | INSERTERS: dict[str, tuple[str, Callable[[dict[str, str]], tuple]]] = { 79 | 'crates.csv': ( 80 | 'INSERT INTO crates VALUES (?,?,?,?,?)', 81 | lambda row: ( 82 | int(row['id']), 83 | row['name'], 84 | row['created_at'], 85 | row['updated_at'], 86 | int(row['downloads']), 87 | ), 88 | ), 89 | 'versions.csv': ( 90 | 'INSERT INTO versions VALUES (?,?,?,?,?,?,?,?,?)', 91 | lambda row: ( 92 | int(row['id']), 93 | int(row['crate_id']), 94 | row['num'], 95 | row['updated_at'], 96 | row['created_at'], 97 | int(row['downloads']), 98 | row['features'], 99 | row['yanked'] == 't', 100 | row['license'], 101 | ), 102 | ), 103 | 'version_downloads.csv': ( 104 | 'INSERT INTO version_downloads VALUES (?,?,?)', 105 | lambda row: ( 106 | int(row['version_id']), 107 | int(row['downloads']), 108 | row['date'], 109 | ) 110 | ), 111 | } 112 | 113 | def __init__(self, *, check: bool=True, cache_mb: int=1024) -> None: 114 | assert not check or self.MTIME_PATH.exists(), 'Database not initialized, please run `sync` subcommand' 115 | super().__init__(str(self.DB_PATH)) 116 | self.executescript(self.INIT_SQL.format(cache_kb=cache_mb * 1024)) 117 | 118 | @classmethod 119 | def sync(cls, *, cache_mb: int=1024) -> None: 120 | cls.DB_DIR.mkdir(exist_ok=True) 121 | last_mtime = cls.MTIME_PATH.read_text() if cls.MTIME_PATH.exists() else None 122 | 123 | noisily('Synchronizing database') 124 | with request.urlopen(cls.DB_URL) as resp: 125 | assert resp.status == 200, f'HTTP failure {resp.status}' 126 | mtime: str = resp.headers['last-modified'] 127 | if mtime == last_mtime: 128 | noisily(f'Database is up-to-date at {mtime}') 129 | return 130 | 131 | noisily(f'Fetching and importing database dump at {mtime}, previous at {last_mtime}') 132 | cls.DB_PATH.unlink(missing_ok=True) 133 | db = CratesioDB(check=False, cache_mb=cache_mb) 134 | 135 | csv.field_size_limit(2 ** 30) # There are large fields. 136 | with tarfile.open(fileobj=resp, mode='r|gz') as tar: 137 | for member in tar: 138 | name = member.path.split('/')[-1] 139 | if name in cls.INSERTERS: 140 | sql, preprocessor = cls.INSERTERS[name] 141 | fileobj = tar.extractfile(member) 142 | assert fileobj is not None 143 | rdr = csv.DictReader(map(bytes.decode, fileobj)) 144 | db.executemany(sql, map(preprocessor, rdr)) 145 | 146 | noisily(f'Creating indices') 147 | db.executescript(cls.INIT_INDEX_SQL) 148 | db.commit() 149 | db.close() 150 | 151 | cls.MTIME_PATH.write_text(mtime) 152 | noisily('Database initialized') 153 | 154 | def update_popular_crates(db: CratesioDB, *, time: str, limit: int) -> None: 155 | class VersMajor(tuple): 156 | RE_SEMVER = re.compile(r'^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$') 157 | 158 | def __new__(cls: type[Self], s: str) -> Self: 159 | m = cls.RE_SEMVER.match(s) 160 | assert m is not None, f'Invalid semver: {s}' 161 | maj, min, pat = map(int, (m[1], m[2], m[3])) 162 | vers = (maj,) if maj else (maj, min) if min else (maj, min, pat) 163 | return super().__new__(cls, vers) 164 | 165 | def __str__(self) -> str: 166 | return '.'.join(map(str, self)) 167 | 168 | # Reversed. 169 | def __lt__(self, rhs: Self) -> bool: 170 | return super().__gt__(rhs) 171 | 172 | noisily('Querying database') 173 | cursor = db.cursor() 174 | cursor.execute(''' 175 | SELECT crates.name, versions.num 176 | FROM version_downloads 177 | JOIN versions ON versions.id == version_id 178 | JOIN crates ON crates.id == versions.crate_id 179 | WHERE version_downloads.date > DATE('now', ?) 180 | GROUP BY version_id 181 | ORDER BY SUM(version_downloads.downloads) DESC 182 | ''', (time,)) 183 | 184 | crates_set: set[tuple[str, VersMajor]] = set() 185 | for row in cursor: 186 | crates_set.add((str(row[0]), VersMajor(row[1]))) 187 | if len(crates_set) == limit: 188 | break 189 | 190 | crates = sorted(crates_set) 191 | 192 | noisily('======== Start of top crates') 193 | for name, vers in crates: 194 | noisily(name, vers) 195 | noisily('======== End of top crates') 196 | 197 | out_path = POPULAR_CRATES_MANIFEST_PATH 198 | tmp_path = out_path.with_suffix('.tmp') 199 | with out_path.open('r') as fin, tmp_path.open('w') as fout: 200 | for line in fin: 201 | fout.write(line) 202 | if line.startswith('[dependencies]'): 203 | break 204 | 205 | last_name: Optional[str] = None 206 | crate_idx = 1 207 | for name, vers in crates: 208 | if last_name != name: 209 | crate_idx = 1 210 | fout.write(f'{name} = "{vers}"\n') 211 | else: 212 | crate_idx += 1 213 | fout.write(f'{name}-{crate_idx} = {{ package = "{name}", version = "{vers}" }}\n') 214 | last_name = name 215 | tmp_path.replace(out_path) 216 | 217 | noisily('Updating lock file') 218 | subprocess.check_call(['cargo', 'update', f'--manifest-path={out_path}']) 219 | 220 | noisily('Verifying crates metadata') 221 | subprocess.check_call( 222 | ['cargo', 'metadata', f'--manifest-path={out_path}', '--format-version=1'], 223 | stdout=subprocess.DEVNULL, 224 | ) 225 | 226 | def list_popular_crates(db: CratesioDB, *, time: str, pat: Optional[str]) -> None: 227 | noisily('Querying database') 228 | cursor = db.cursor() 229 | cursor.execute(''' 230 | SELECT crates.name, SUM(version_downloads.downloads) AS crate_downloads 231 | FROM crates 232 | JOIN versions ON versions.crate_id == crates.id 233 | JOIN version_downloads ON version_downloads.version_id == versions.id 234 | WHERE crates.name LIKE ? AND version_downloads.date > DATE('now', ?) 235 | GROUP BY versions.crate_id 236 | ORDER BY crate_downloads DESC 237 | ''', (pat if pat is not None else '%', time)) 238 | try: 239 | for row in cursor: 240 | print(row[0], row[1]) 241 | except BrokenPipeError as _: 242 | # Suppress backtrace. 243 | exit(1) 244 | 245 | def update_proc_macro_crates(db: CratesioDB, *, concurrency: int) -> None: 246 | CRATES_TOML_DIR.mkdir(exist_ok=True) 247 | 248 | noisily('Querying database') 249 | cursor = db.cursor() 250 | cursor.execute(''' 251 | SELECT crates.name, versions.num AS max_vers, MAX(versions.created_at) 252 | FROM versions 253 | JOIN crates ON crates.id == crate_id 254 | WHERE NOT yanked 255 | GROUP BY crate_id 256 | ORDER BY crates.name ASC 257 | ''') 258 | 259 | RE_ITEMS = re.compile(r'"([^"]*)"', re.S) 260 | proc_macro_crates: set[str] = set(m[1] for m in RE_ITEMS.finditer(PROC_MACRO_LIST_PATH.read_text())) 261 | 262 | async def load_or_fetch(sema: asyncio.Semaphore, name: str, version: str) -> None: 263 | try: 264 | out_path = CRATES_TOML_DIR / f'{name}.toml' 265 | if not out_path.exists(): 266 | noisily(f'GET {name} {version}') 267 | url = CRATE_TARBALL_URL.format(name=name, version=version) 268 | async with aiohttp.request(method='GET', url=url) as resp: 269 | resp.raise_for_status() 270 | data = await resp.read() 271 | 272 | with tarfile.open(mode='r|gz', fileobj=BytesIO(data)) as tar: 273 | for member in tar: 274 | segments = member.path.split('/') 275 | if len(segments) == 2 and segments[-1] == 'Cargo.toml': 276 | f = tar.extractfile(member) 277 | assert f is not None 278 | tmp_path = out_path.with_suffix('.tmp') 279 | tmp_path.write_bytes(f.read()) 280 | tmp_path.replace(out_path) 281 | break 282 | else: 283 | assert False, 'No Cargo.toml found' 284 | except (aiohttp.ClientError, tarfile.TarError, UnicodeDecodeError, AssertionError) as exc: 285 | print(f'For {name} {version}: {exc}', file=sys.stderr) 286 | return 287 | finally: 288 | sema.release() 289 | 290 | try: 291 | # Must exist if we are here. 292 | with open(out_path, 'r') as fin: 293 | manifest = toml.load(fin) 294 | lib = manifest.get('lib', {}) 295 | if isinstance(lib, dict) and lib.get('proc-macro', False) is True: 296 | proc_macro_crates.add(name) 297 | except (UnicodeDecodeError, toml.TomlDecodeError) as exc: 298 | print(f'For cached {name}: {exc}', file=sys.stderr) 299 | return 300 | 301 | async def proc() -> None: 302 | sema = asyncio.Semaphore(concurrency) 303 | for row in cursor: 304 | name: str = row[0] 305 | vers: str = row[1] 306 | await sema.acquire() 307 | asyncio.create_task(load_or_fetch(sema, name, vers)) 308 | 309 | # Wait until all done. 310 | for _ in range(concurrency): 311 | await sema.acquire() 312 | 313 | noisily('Retrieving metadata') 314 | asyncio.run(proc()) 315 | 316 | noisily(f'Writing to {PROC_MACRO_LIST_PATH}') 317 | with PROC_MACRO_LIST_PATH.open('w') as fout: 318 | fout.write('[\n') 319 | for name in sorted(proc_macro_crates): 320 | assert '"' not in name 321 | fout.write(f'"{name}"\n') 322 | fout.write(']\n') 323 | 324 | def main() -> None: 325 | parser = argparse.ArgumentParser(description='Metadata updater and utilities using crates.io database dump') 326 | parser.add_argument('--cache-mb', type=int, required=False, default=1024, help='Sqlite cache size in MiB') 327 | subparser = parser.add_subparsers(required=True) 328 | 329 | p = subparser.add_parser('sync', help='Synchronize or initialize the database') 330 | p.set_defaults(sync=True) 331 | 332 | p = subparser.add_parser('update-popular-crates', help='Update popular crates cache') 333 | p.add_argument('--limit', type=int, required=False, default=256, help='Number of top crates to cache') 334 | p.add_argument('--time', type=str, default='-90 days', help='Time period to count recent downloads') 335 | p.set_defaults(fn=update_popular_crates) 336 | 337 | p = subparser.add_parser('list-popular-crates', help='Print popular crates with download counts in a given period') 338 | p.add_argument('--time', type=str, default='-90 days', help='Time period to count recent downloads') 339 | p.add_argument('--pat', type=str, default=None, help='Crate name pattern to filter for SQL "LIKE"') 340 | p.set_defaults(fn=list_popular_crates) 341 | 342 | p = subparser.add_parser('update-proc-macro-crates', help='Update the list of proc-macro crates') 343 | p.add_argument('--concurrency', type=int, default=16, help='Connection concurrency') 344 | p.set_defaults(fn=update_proc_macro_crates) 345 | 346 | args = parser.parse_args() 347 | if 'sync' in args: 348 | CratesioDB.sync(cache_mb=args.cache_mb) 349 | else: 350 | subargs = vars(args) 351 | with CratesioDB(cache_mb=subargs.pop('cache_mb')) as db: 352 | subargs.pop('fn')(db, **subargs) 353 | 354 | main() 355 | -------------------------------------------------------------------------------- /tests/build-deps/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "build-deps" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "semver", 10 | ] 11 | 12 | [[package]] 13 | name = "semver" 14 | version = "1.0.12" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 17 | -------------------------------------------------------------------------------- /tests/build-deps/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "build-deps" 3 | version = "0.0.0" 4 | edition = "2015" 5 | 6 | [build-dependencies] 7 | semver = "1" 8 | -------------------------------------------------------------------------------- /tests/build-deps/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let s = semver::Version::new(1, 2, 3).to_string(); 3 | println!("cargo:rustc-cfg=result={:?}", s); 4 | } 5 | -------------------------------------------------------------------------------- /tests/build-deps/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(result = "1.2.3")] 2 | fn main() { 3 | println!("Hello, world!"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/build-feature-env-vars/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "build-feature-env-vars" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /tests/build-feature-env-vars/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "build-feature-env-vars" 3 | version = "0.0.0" 4 | edition = "2015" 5 | 6 | [features] 7 | default = ["foo-bar"] 8 | foo-bar = [] 9 | quux = [] 10 | -------------------------------------------------------------------------------- /tests/build-feature-env-vars/build.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | 3 | fn main() { 4 | if var("CARGO_FEATURE_QUUX").is_err() { 5 | if let Ok(s) = var("CARGO_FEATURE_FOO_BAR") { 6 | println!("cargo:rustc-cfg=result={:?}", s); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/build-feature-env-vars/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(result = "1")] 2 | fn main() { 3 | println!("Hello, world!"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/cap-lints/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cap-lints" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "want", 10 | ] 11 | 12 | [[package]] 13 | name = "cfg-if" 14 | version = "1.0.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 17 | 18 | [[package]] 19 | name = "log" 20 | version = "0.4.17" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 23 | dependencies = [ 24 | "cfg-if", 25 | ] 26 | 27 | [[package]] 28 | name = "try-lock" 29 | version = "0.2.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 32 | 33 | [[package]] 34 | name = "want" 35 | version = "0.3.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 38 | dependencies = [ 39 | "log", 40 | "try-lock", 41 | ] 42 | -------------------------------------------------------------------------------- /tests/cap-lints/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-lints" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | # error: use of deprecated associated function `try_lock::TryLock::::try_lock_order`: This method is actually unsafe because it unsafely allows the use of weaker memory ordering. Please use try_lock_explicit instead 8 | # --> src/lib.rs:209:63 9 | # | 10 | # 209 | if let Some(mut locked) = self.inner.task.try_lock_order(SeqCst, SeqCst) { 11 | # | ^^^^^^^^^^^^^^ 12 | # | 13 | # note: the lint level is defined here 14 | # --> src/lib.rs:2:9 15 | # | 16 | # 2 | #![deny(warnings)] 17 | # | ^^^^^^^^ 18 | # = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` 19 | # 20 | # https://github.com/seanmonstar/want/blob/v0.3.0/src/lib.rs#L352 21 | want = "=0.3.0" 22 | -------------------------------------------------------------------------------- /tests/cap-lints/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = want::new(); 3 | println!("Hello, world!"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/crate-names/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "RustyXML" 7 | version = "0.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8b5ace29ee3216de37c0546865ad08edef58b0f9e76838ed8959a84a990e58c5" 10 | 11 | [[package]] 12 | name = "b" 13 | version = "0.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ad71822f94ff0251011da9d7c63248c2520e6a69e56d457be0679b4fe81cbada" 16 | 17 | [[package]] 18 | name = "cc" 19 | version = "1.0.73" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 22 | 23 | [[package]] 24 | name = "crate-names" 25 | version = "0.0.0" 26 | dependencies = [ 27 | "RustyXML", 28 | "b", 29 | "cc", 30 | "fnv", 31 | ] 32 | 33 | [[package]] 34 | name = "fnv" 35 | version = "1.0.7" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 38 | -------------------------------------------------------------------------------- /tests/crate-names/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crate-names" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | b = "=0.2.0" 8 | cc = "=1.0.73" 9 | fnv = "=1.0.7" 10 | RustyXML = "=0.3.0" # Should be lowered. 11 | -------------------------------------------------------------------------------- /tests/crate-names/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hasher; 2 | 3 | fn main() { 4 | assert_eq!(b::B, "🅱️"); 5 | let _ = cc::Build::new(); 6 | assert_eq!(fnv::FnvHasher::with_key(42).finish(), 42); 7 | assert_eq!(xml::escape("<"), "<"); 8 | println!("Hello, world!"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/custom-lib-name/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "angle" 7 | version = "0.4.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ba2ba87287a952ce1802117f6201af6ff3f32dd372f42dbf53217e900bac7881" 10 | dependencies = [ 11 | "num-traits", 12 | "serde", 13 | "serde_derive", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "color-rs" 24 | version = "0.5.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "00c7ad08f8b0b391ab2e94aea63cd01f1d821775caee42c0eb62367872a18a5f" 27 | dependencies = [ 28 | "angle", 29 | "num-traits", 30 | "serde", 31 | "serde_derive", 32 | ] 33 | 34 | [[package]] 35 | name = "color-rs" 36 | version = "0.6.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "59f43d9768f46589e0b9f246486f49a5b841b29651503c919d0ed3f1e679fc70" 39 | dependencies = [ 40 | "angle", 41 | "half", 42 | "num-traits", 43 | "serde", 44 | "serde_derive", 45 | ] 46 | 47 | [[package]] 48 | name = "custom-lib-name" 49 | version = "0.0.0" 50 | dependencies = [ 51 | "color-rs 0.5.0", 52 | "color-rs 0.6.1", 53 | ] 54 | 55 | [[package]] 56 | name = "half" 57 | version = "1.8.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 60 | 61 | [[package]] 62 | name = "num-traits" 63 | version = "0.2.15" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 66 | dependencies = [ 67 | "autocfg", 68 | ] 69 | 70 | [[package]] 71 | name = "proc-macro2" 72 | version = "1.0.40" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 75 | dependencies = [ 76 | "unicode-ident", 77 | ] 78 | 79 | [[package]] 80 | name = "quote" 81 | version = "1.0.20" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 84 | dependencies = [ 85 | "proc-macro2", 86 | ] 87 | 88 | [[package]] 89 | name = "serde" 90 | version = "1.0.139" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" 93 | 94 | [[package]] 95 | name = "serde_derive" 96 | version = "1.0.139" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" 99 | dependencies = [ 100 | "proc-macro2", 101 | "quote", 102 | "syn", 103 | ] 104 | 105 | [[package]] 106 | name = "syn" 107 | version = "1.0.98" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 110 | dependencies = [ 111 | "proc-macro2", 112 | "quote", 113 | "unicode-ident", 114 | ] 115 | 116 | [[package]] 117 | name = "unicode-ident" 118 | version = "1.0.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 121 | -------------------------------------------------------------------------------- /tests/custom-lib-name/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-lib-name" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | name = "custom" 8 | 9 | [dependencies] 10 | color-rs = "=0.6.1" 11 | renamed = { package = "color-rs", version = "=0.5.0" } 12 | -------------------------------------------------------------------------------- /tests/custom-lib-name/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn custom() -> i32 { 2 | 42 3 | } 4 | -------------------------------------------------------------------------------- /tests/custom-lib-name/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | assert_eq!(color::consts::PURPLE, color::Rgb::new(0x80, 0x00, 0x80)); 3 | assert_eq!(renamed::consts::PURPLE, renamed::Rgb::new(0x80, 0x00, 0x80)); 4 | assert_eq!(custom::custom(), 42); 5 | println!("Hello, world!"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, self, inputs, defaultRegistries }: 2 | let 3 | inherit (pkgs.lib) mapAttrs attrNames attrValues assertMsg head mapAttrsToList; 4 | inherit (self.lib.${pkgs.system}) mkRustPackageOrWorkspace; 5 | inherit (self.packages.${pkgs.system}) noc; 6 | 7 | git-semver-1-0-0 = builtins.fetchTarball { 8 | url = "https://github.com/dtolnay/semver/archive/1.0.0/master.tar.gz"; 9 | sha256 = "0s7gwj5l0h98spgm7vyxak9z3hgrachwxbnf1fpry5diz939x8n4"; 10 | }; 11 | 12 | git-semver-1-0-12 = builtins.fetchTarball { 13 | url = "https://github.com/dtolnay/semver/archive/1.0.4/master.tar.gz"; 14 | sha256 = "1l2nkfmjgz2zkqw03hmy66q0v1rxvs7fc4kh63ph4lf1924wrmix"; 15 | }; 16 | 17 | gitSrcs = { 18 | "https://github.com/dtolnay/semver?tag=1.0.0" = git-semver-1-0-0; 19 | "http://github.com/dtolnay/semver" = git-semver-1-0-12; # v1, v2 20 | "http://github.com/dtolnay/semver?branch=master" = git-semver-1-0-12; # v3 21 | "ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" = git-semver-1-0-0; 22 | "ssh://git@github.com/dtolnay/semver" = git-semver-1-0-12; 23 | }; 24 | 25 | extraRegistries = { 26 | "https://www.github.com/rust-lang/crates.io-index" = 27 | head (attrValues defaultRegistries); 28 | }; 29 | 30 | shouldBeHelloWorld = drv: pkgs.runCommand "${drv.name}" {} '' 31 | binaries=(${drv.bin}/bin/*) 32 | [[ ''${#binaries[@]} == 1 ]] 33 | got="$(''${binaries[0]})" 34 | expect="Hello, world!" 35 | echo "Got : $got" 36 | echo "Expect: $expect" 37 | [[ "$got" == "$expect" ]] 38 | touch $out 39 | ''; 40 | 41 | mkHelloWorldTest = src: 42 | let 43 | ws = mkRustPackageOrWorkspace { 44 | inherit src gitSrcs extraRegistries; 45 | }; 46 | profiles = mapAttrs (_: pkgs: shouldBeHelloWorld (head (attrValues pkgs))) ws; 47 | in { 48 | inherit (profiles) dev release; 49 | }; 50 | 51 | mkWorkspaceTest = src: expectMembers: let 52 | ws = mkRustPackageOrWorkspace { inherit src; }; 53 | gotMembers = attrNames ws.dev; 54 | in 55 | assert assertMsg (gotMembers == expectMembers) '' 56 | Member assertion failed. 57 | expect: ${toString expectMembers} 58 | got: ${toString gotMembers} 59 | ''; 60 | ws; 61 | 62 | # Recursive Nix setup. 63 | # https://github.com/NixOS/nixpkgs/blob/e966ab3965a656efdd40b6ae0d8cec6183972edc/pkgs/top-level/make-tarball.nix#L45-L48 64 | mkGenInit = name: path: 65 | pkgs.runCommand "gen-${name}" { 66 | nativeBuildInputs = [ noc pkgs.nix ]; 67 | checkFlags = 68 | mapAttrsToList (from: to: "--override-input ${from} ${to}") { 69 | inherit (inputs) nixpkgs flake-utils; 70 | nocargo = self; 71 | "nocargo/registry-crates-io" = inputs.registry-crates-io; 72 | registry-1 = inputs.registry-crates-io; 73 | git-1 = git-semver-1-0-0; 74 | git-2 = git-semver-1-0-0; 75 | git-3 = git-semver-1-0-0; 76 | git-4 = git-semver-1-0-0; 77 | }; 78 | } '' 79 | cp -r ${path} src 80 | chmod -R u+w src 81 | cd src 82 | 83 | echo "generating flake.nix" 84 | noc init 85 | cat flake.nix 86 | install -D flake.nix $out/flake.nix 87 | 88 | echo "checking with 'nix flake check'" 89 | export NIX_STATE_DIR=$TMPDIR/nix/var 90 | export NIX_PATH= 91 | export HOME=$TMPDIR 92 | nix-store --init 93 | nixFlags=( 94 | --offline 95 | --option build-users-group "" 96 | --option experimental-features "ca-derivations nix-command flakes" 97 | --store $TMPDIR/nix/store 98 | ) 99 | 100 | nix flake check \ 101 | --no-build \ 102 | --show-trace \ 103 | $checkFlags \ 104 | "''${nixFlags[@]}" 105 | ''; 106 | 107 | in 108 | { 109 | _1000-hello-worlds = mapAttrs (name: path: mkHelloWorldTest path) { 110 | build-deps = ./build-deps; 111 | build-feature-env-vars = ./build-feature-env-vars; 112 | cap-lints = ./cap-lints; 113 | crate-names = ./crate-names; 114 | custom-lib-name = ./custom-lib-name; 115 | dependency-v1 = ./dependency-v1; 116 | dependency-v2 = ./dependency-v2; 117 | dependency-v3 = ./dependency-v3; 118 | features = ./features; 119 | libz-dynamic = ./libz-dynamic; 120 | libz-static = ./libz-static; 121 | lto-fat = ./lto-fat; 122 | lto-proc-macro = ./lto-proc-macro; 123 | lto-thin = ./lto-thin; 124 | tokio-app = ./tokio-app; 125 | } // { 126 | workspace-inline = mkWorkspaceTest ./workspace-inline [ "bar" "baz" "foo" ]; 127 | workspace-proc-macro-lto = mkWorkspaceTest ./workspace-proc-macro-lto [ "acro" "procm" ]; 128 | workspace-virtual = mkWorkspaceTest ./workspace-virtual [ "bar" "foo" ]; 129 | }; 130 | 131 | _1100-gen-init = mapAttrs mkGenInit { 132 | dependency-v1 = ./dependency-v1; 133 | dependency-v2 = ./dependency-v2; 134 | dependency-v3 = ./dependency-v3; 135 | features = ./features; 136 | 137 | workspace-virtual = ./workspace-virtual; 138 | workspace-inline = ./workspace-inline; 139 | }; 140 | } 141 | -------------------------------------------------------------------------------- /tests/dependency-v1/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "dependencies" 5 | version = "0.0.0" 6 | dependencies = [ 7 | "semver 1.0.0 (git+https://github.com/dtolnay/semver?tag=1.0.0)", 8 | "semver 1.0.0 (git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd)", 9 | "semver 1.0.12 (git+http://github.com/dtolnay/semver)", 10 | "semver 1.0.12 (git+ssh://git@github.com/dtolnay/semver)", 11 | "semver 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "semver 1.0.12 (registry+https://www.github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "semver" 17 | version = "1.0.0" 18 | source = "git+https://github.com/dtolnay/semver?tag=1.0.0#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 19 | 20 | [[package]] 21 | name = "semver" 22 | version = "1.0.0" 23 | source = "git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 24 | 25 | [[package]] 26 | name = "semver" 27 | version = "1.0.12" 28 | source = "git+http://github.com/dtolnay/semver#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 29 | 30 | [[package]] 31 | name = "semver" 32 | version = "1.0.12" 33 | source = "git+ssh://git@github.com/dtolnay/semver#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 34 | 35 | [[package]] 36 | name = "semver" 37 | version = "1.0.12" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "semver" 42 | version = "1.0.12" 43 | source = "registry+https://www.github.com/rust-lang/crates.io-index" 44 | 45 | [metadata] 46 | "checksum semver 1.0.0 (git+https://github.com/dtolnay/semver?tag=1.0.0)" = "" 47 | "checksum semver 1.0.0 (git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd)" = "" 48 | "checksum semver 1.0.12 (git+http://github.com/dtolnay/semver)" = "" 49 | "checksum semver 1.0.12 (git+ssh://git@github.com/dtolnay/semver)" = "" 50 | "checksum semver 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 51 | "checksum semver 1.0.12 (registry+https://www.github.com/rust-lang/crates.io-index)" = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 52 | -------------------------------------------------------------------------------- /tests/dependency-v1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dependencies" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | cratesio = { package = "semver", version = "1" } 8 | registry-index = { package = "semver", version = "1", registry-index = "https://www.github.com/rust-lang/crates.io-index" } 9 | git-tag = { package = "semver", git = "https://github.com/dtolnay/semver", tag = "1.0.0" } 10 | git-branch = { package = "semver", git = "http://github.com/dtolnay/semver", branch = "master" } 11 | git-rev = { package = "semver", git = "ssh://git@github.com/dtolnay/semver", rev = "a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" } 12 | git-head = { package = "semver", git = "ssh://git@github.com/dtolnay/semver" } 13 | -------------------------------------------------------------------------------- /tests/dependency-v1/README.md: -------------------------------------------------------------------------------- 1 | This `Cargo.lock` is generated by rust 1.37.0. 2 | -------------------------------------------------------------------------------- /tests/dependency-v1/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | cratesio::Version::parse("1.2.3").unwrap(); 3 | registry_index::Version::parse("1.2.3").unwrap(); 4 | git_tag::Version::parse("1.2.3").unwrap(); 5 | git_branch::Version::parse("1.2.3").unwrap(); 6 | git_rev::Version::parse("1.2.3").unwrap(); 7 | git_head::Version::parse("1.2.3").unwrap(); 8 | println!("Hello, world!"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/dependency-v2/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "dependencies" 5 | version = "0.0.0" 6 | dependencies = [ 7 | "semver 1.0.0 (git+https://github.com/dtolnay/semver?tag=1.0.0)", 8 | "semver 1.0.0 (git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd)", 9 | "semver 1.0.12 (git+http://github.com/dtolnay/semver)", 10 | "semver 1.0.12 (git+ssh://git@github.com/dtolnay/semver)", 11 | "semver 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "semver 1.0.12 (registry+https://www.github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "semver" 17 | version = "1.0.0" 18 | source = "git+https://github.com/dtolnay/semver?tag=1.0.0#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 19 | 20 | [[package]] 21 | name = "semver" 22 | version = "1.0.0" 23 | source = "git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 24 | 25 | [[package]] 26 | name = "semver" 27 | version = "1.0.12" 28 | source = "git+http://github.com/dtolnay/semver#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 29 | 30 | [[package]] 31 | name = "semver" 32 | version = "1.0.12" 33 | source = "git+ssh://git@github.com/dtolnay/semver#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 34 | 35 | [[package]] 36 | name = "semver" 37 | version = "1.0.12" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 40 | 41 | [[package]] 42 | name = "semver" 43 | version = "1.0.12" 44 | source = "registry+https://www.github.com/rust-lang/crates.io-index" 45 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 46 | -------------------------------------------------------------------------------- /tests/dependency-v2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dependencies" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | cratesio = { package = "semver", version = "1" } 8 | registry-index = { package = "semver", version = "1", registry-index = "https://www.github.com/rust-lang/crates.io-index" } 9 | git-tag = { package = "semver", git = "https://github.com/dtolnay/semver", tag = "1.0.0" } 10 | git-branch = { package = "semver", git = "http://github.com/dtolnay/semver", branch = "master" } 11 | git-rev = { package = "semver", git = "ssh://git@github.com/dtolnay/semver", rev = "a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" } 12 | git-head = { package = "semver", git = "ssh://git@github.com/dtolnay/semver" } 13 | -------------------------------------------------------------------------------- /tests/dependency-v2/README.md: -------------------------------------------------------------------------------- 1 | This `Cargo.lock` is generated by rust 1.41.0. 2 | -------------------------------------------------------------------------------- /tests/dependency-v2/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | cratesio::Version::parse("1.2.3").unwrap(); 3 | registry_index::Version::parse("1.2.3").unwrap(); 4 | git_tag::Version::parse("1.2.3").unwrap(); 5 | git_branch::Version::parse("1.2.3").unwrap(); 6 | git_rev::Version::parse("1.2.3").unwrap(); 7 | git_head::Version::parse("1.2.3").unwrap(); 8 | println!("Hello, world!"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/dependency-v3/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "dependencies" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "semver 1.0.0 (git+https://github.com/dtolnay/semver?tag=1.0.0)", 10 | "semver 1.0.0 (git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd)", 11 | "semver 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "semver 1.0.12 (registry+https://www.github.com/rust-lang/crates.io-index)", 13 | "semver 1.0.12 (git+http://github.com/dtolnay/semver?branch=master)", 14 | "semver 1.0.12 (git+ssh://git@github.com/dtolnay/semver)", 15 | ] 16 | 17 | [[package]] 18 | name = "semver" 19 | version = "1.0.0" 20 | source = "git+https://github.com/dtolnay/semver?tag=1.0.0#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 21 | 22 | [[package]] 23 | name = "semver" 24 | version = "1.0.0" 25 | source = "git+ssh://git@github.com/dtolnay/semver?rev=a2ce5777dcd455246e4650e36dde8e2e96fcb3fd#a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" 26 | 27 | [[package]] 28 | name = "semver" 29 | version = "1.0.12" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 32 | 33 | [[package]] 34 | name = "semver" 35 | version = "1.0.12" 36 | source = "registry+https://www.github.com/rust-lang/crates.io-index" 37 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 38 | 39 | [[package]] 40 | name = "semver" 41 | version = "1.0.12" 42 | source = "git+http://github.com/dtolnay/semver?branch=master#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 43 | 44 | [[package]] 45 | name = "semver" 46 | version = "1.0.12" 47 | source = "git+ssh://git@github.com/dtolnay/semver#a6425e6f41ddc81c6d6dd60c68248e0f0ef046c7" 48 | -------------------------------------------------------------------------------- /tests/dependency-v3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dependencies" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | cratesio = { package = "semver", version = "1" } 8 | registry-index = { package = "semver", version = "1", registry-index = "https://www.github.com/rust-lang/crates.io-index" } 9 | git-tag = { package = "semver", git = "https://github.com/dtolnay/semver", tag = "1.0.0" } 10 | git-branch = { package = "semver", git = "http://github.com/dtolnay/semver", branch = "master" } 11 | git-rev = { package = "semver", git = "ssh://git@github.com/dtolnay/semver", rev = "a2ce5777dcd455246e4650e36dde8e2e96fcb3fd" } 12 | git-head = { package = "semver", git = "ssh://git@github.com/dtolnay/semver" } 13 | -------------------------------------------------------------------------------- /tests/dependency-v3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | cratesio::Version::parse("1.2.3").unwrap(); 3 | registry_index::Version::parse("1.2.3").unwrap(); 4 | git_tag::Version::parse("1.2.3").unwrap(); 5 | git_branch::Version::parse("1.2.3").unwrap(); 6 | git_rev::Version::parse("1.2.3").unwrap(); 7 | git_head::Version::parse("1.2.3").unwrap(); 8 | println!("Hello, world!"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/fake-semver/Cargo.toml: -------------------------------------------------------------------------------- 1 | # This crate is for tests in `support.nix` and will not be really built. 2 | [package] 3 | name = "semver" 4 | version = "1.0.0" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/fake-semver/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/features/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "semver" 7 | version = "1.0.12" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" 10 | dependencies = [ 11 | "serde", 12 | ] 13 | 14 | [[package]] 15 | name = "serde" 16 | version = "1.0.139" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" 19 | 20 | [[package]] 21 | name = "simple-features" 22 | version = "0.0.0" 23 | dependencies = [ 24 | "semver", 25 | ] 26 | -------------------------------------------------------------------------------- /tests/features/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-features" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | semver = { version = "1.0.12", optional = true } 8 | 9 | [features] 10 | default = [ "a" ] 11 | a = ["default", "semver/serde"] 12 | b = [] 13 | -------------------------------------------------------------------------------- /tests/features/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "a", not(feature = "b")))] 2 | fn main() { 3 | println!("Hello, world!"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/libz-dynamic/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cc" 7 | version = "1.0.73" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 10 | 11 | [[package]] 12 | name = "libz-dynamic" 13 | version = "0.0.0" 14 | dependencies = [ 15 | "libz-sys", 16 | ] 17 | 18 | [[package]] 19 | name = "libz-sys" 20 | version = "1.1.6" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" 23 | dependencies = [ 24 | "cc", 25 | "pkg-config", 26 | "vcpkg", 27 | ] 28 | 29 | [[package]] 30 | name = "pkg-config" 31 | version = "0.3.25" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 34 | 35 | [[package]] 36 | name = "vcpkg" 37 | version = "0.2.15" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 40 | -------------------------------------------------------------------------------- /tests/libz-dynamic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libz-dynamic" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | libz-sys = { version = "=1.1.6", default-features = false } 8 | -------------------------------------------------------------------------------- /tests/libz-dynamic/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | std::env::var("DEP_Z_INCLUDE").expect_err("Dynamic linking should not set DEP_Z_INCLUDE"); 3 | println!("cargo:rustc-env=OKAY="); 4 | } 5 | -------------------------------------------------------------------------------- /tests/libz-dynamic/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let crc_init = unsafe { libz_sys::crc32(0, "foo".as_ptr() as _, 3) }; 3 | assert_eq!(crc_init, 2356372769); 4 | assert_eq!(env!("OKAY"), ""); 5 | println!("Hello, world!"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/libz-static/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cc" 7 | version = "1.0.73" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 10 | 11 | [[package]] 12 | name = "libz-static" 13 | version = "0.0.0" 14 | dependencies = [ 15 | "libz-sys", 16 | ] 17 | 18 | [[package]] 19 | name = "libz-sys" 20 | version = "1.1.3" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" 23 | dependencies = [ 24 | "cc", 25 | "pkg-config", 26 | "vcpkg", 27 | ] 28 | 29 | [[package]] 30 | name = "pkg-config" 31 | version = "0.3.25" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 34 | 35 | [[package]] 36 | name = "vcpkg" 37 | version = "0.2.15" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 40 | -------------------------------------------------------------------------------- /tests/libz-static/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libz-static" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | libz-sys = { version = "=1.1.3", default-features = false, features = ["static"] } 8 | -------------------------------------------------------------------------------- /tests/libz-static/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let z_include = std::env::var("DEP_Z_INCLUDE").unwrap(); 3 | let header = std::fs::read_to_string(z_include + "/zlib.h").unwrap(); 4 | let mut lines = header.lines(); 5 | assert_eq!( 6 | lines.next().unwrap(), 7 | "/* zlib.h -- interface of the 'zlib' general purpose compression library" 8 | ); 9 | assert_eq!( 10 | lines.next().unwrap(), 11 | " version 1.2.11, January 15th, 2017", 12 | "bundled libz MUST be 1.2.11", 13 | ); 14 | println!("cargo:rustc-env=OKAY="); 15 | } 16 | -------------------------------------------------------------------------------- /tests/libz-static/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Only available in the build script. 2 | const _: [(); 1] = [(); option_env!("DEP_Z_INCLUDE").is_none() as usize]; 3 | -------------------------------------------------------------------------------- /tests/libz-static/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Only available in the build script. 3 | assert!(option_env!("DEP_Z_INCLUDE").is_none()); 4 | 5 | let crc_init = unsafe { libz_sys::crc32(0, "foo".as_ptr() as _, 3) }; 6 | assert_eq!(crc_init, 2356372769); 7 | assert_eq!(env!("OKAY"), ""); 8 | println!("Hello, world!"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/lto-fat/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "lto-fat" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "semver", 10 | ] 11 | 12 | [[package]] 13 | name = "semver" 14 | version = "1.0.13" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" 17 | -------------------------------------------------------------------------------- /tests/lto-fat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lto-fat" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | semver = "1" 8 | 9 | [profile.release] 10 | lto = "fat" 11 | -------------------------------------------------------------------------------- /tests/lto-fat/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | assert_eq!( 3 | semver::Version::parse("1.2.3").unwrap().to_string(), 4 | "1.2.3" 5 | ); 6 | println!("Hello, world!"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/lto-proc-macro/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "lto-proc-macro" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "thiserror", 10 | ] 11 | 12 | [[package]] 13 | name = "proc-macro2" 14 | version = "1.0.43" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 17 | dependencies = [ 18 | "unicode-ident", 19 | ] 20 | 21 | [[package]] 22 | name = "quote" 23 | version = "1.0.21" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 26 | dependencies = [ 27 | "proc-macro2", 28 | ] 29 | 30 | [[package]] 31 | name = "syn" 32 | version = "1.0.99" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 35 | dependencies = [ 36 | "proc-macro2", 37 | "quote", 38 | "unicode-ident", 39 | ] 40 | 41 | [[package]] 42 | name = "thiserror" 43 | version = "1.0.35" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" 46 | dependencies = [ 47 | "thiserror-impl", 48 | ] 49 | 50 | [[package]] 51 | name = "thiserror-impl" 52 | version = "1.0.35" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" 55 | dependencies = [ 56 | "proc-macro2", 57 | "quote", 58 | "syn", 59 | ] 60 | 61 | [[package]] 62 | name = "unicode-ident" 63 | version = "1.0.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" 66 | -------------------------------------------------------------------------------- /tests/lto-proc-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lto-proc-macro" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | thiserror = "1" 8 | 9 | [profile.release] 10 | lto = "thin" 11 | -------------------------------------------------------------------------------- /tests/lto-proc-macro/src/main.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, thiserror::Error)] 2 | enum Error { 3 | #[error("Hello, {0}!")] 4 | Hello(&'static str), 5 | } 6 | 7 | fn main() { 8 | println!("{}", Error::Hello("world")); 9 | } 10 | -------------------------------------------------------------------------------- /tests/lto-thin/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "lto-thin" 7 | version = "0.0.0" 8 | dependencies = [ 9 | "semver", 10 | ] 11 | 12 | [[package]] 13 | name = "semver" 14 | version = "1.0.13" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" 17 | -------------------------------------------------------------------------------- /tests/lto-thin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lto-thin" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | semver = "1" 8 | 9 | [profile.release] 10 | lto = "thin" 11 | -------------------------------------------------------------------------------- /tests/lto-thin/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | assert_eq!( 3 | semver::Version::parse("1.2.3").unwrap().to_string(), 4 | "1.2.3" 5 | ); 6 | println!("Hello, world!"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/tokio-app/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "hermit-abi" 13 | version = "0.1.19" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 16 | dependencies = [ 17 | "libc", 18 | ] 19 | 20 | [[package]] 21 | name = "libc" 22 | version = "0.2.126" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 25 | 26 | [[package]] 27 | name = "num_cpus" 28 | version = "1.13.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 31 | dependencies = [ 32 | "hermit-abi", 33 | "libc", 34 | ] 35 | 36 | [[package]] 37 | name = "once_cell" 38 | version = "1.13.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 41 | 42 | [[package]] 43 | name = "pin-project-lite" 44 | version = "0.2.9" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 47 | 48 | [[package]] 49 | name = "proc-macro2" 50 | version = "1.0.40" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 53 | dependencies = [ 54 | "unicode-ident", 55 | ] 56 | 57 | [[package]] 58 | name = "quote" 59 | version = "1.0.20" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 62 | dependencies = [ 63 | "proc-macro2", 64 | ] 65 | 66 | [[package]] 67 | name = "syn" 68 | version = "1.0.98" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 71 | dependencies = [ 72 | "proc-macro2", 73 | "quote", 74 | "unicode-ident", 75 | ] 76 | 77 | [[package]] 78 | name = "tokio" 79 | version = "1.20.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" 82 | dependencies = [ 83 | "autocfg", 84 | "num_cpus", 85 | "once_cell", 86 | "pin-project-lite", 87 | "tokio-macros", 88 | ] 89 | 90 | [[package]] 91 | name = "tokio-app" 92 | version = "0.0.0" 93 | dependencies = [ 94 | "tokio", 95 | ] 96 | 97 | [[package]] 98 | name = "tokio-macros" 99 | version = "1.8.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 102 | dependencies = [ 103 | "proc-macro2", 104 | "quote", 105 | "syn", 106 | ] 107 | 108 | [[package]] 109 | name = "unicode-ident" 110 | version = "1.0.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 113 | -------------------------------------------------------------------------------- /tests/tokio-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-app" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = [ "rt-multi-thread", "macros", "time" ], default-features = false } 8 | -------------------------------------------------------------------------------- /tests/tokio-app/src/main.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() { 3 | tokio::time::sleep(std::time::Duration::from_millis(100)).await; 4 | println!("Hello, world!"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/workspace-inline/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bar" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "baz" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "bar", 14 | ] 15 | 16 | [[package]] 17 | name = "foo" 18 | version = "0.1.0" 19 | dependencies = [ 20 | "bar", 21 | ] 22 | -------------------------------------------------------------------------------- /tests/workspace-inline/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "foo" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bar = { path = "bar" } 8 | 9 | [workspace] 10 | members = [".", "b*"] 11 | -------------------------------------------------------------------------------- /tests/workspace-inline/bar/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bar" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/workspace-inline/bar/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn hello() -> &'static str { 2 | "Hello" 3 | } 4 | -------------------------------------------------------------------------------- /tests/workspace-inline/baz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "baz" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bar = { path = "../bar" } 8 | -------------------------------------------------------------------------------- /tests/workspace-inline/baz/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn show(s: String) { 2 | assert!(s.starts_with(bar::hello())); 3 | println!("{}", s); 4 | } 5 | -------------------------------------------------------------------------------- /tests/workspace-inline/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("{}, world!", bar::hello()); 3 | } 4 | -------------------------------------------------------------------------------- /tests/workspace-proc-macro-lto/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "acro" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "procm", 10 | ] 11 | 12 | [[package]] 13 | name = "procm" 14 | version = "0.1.0" 15 | -------------------------------------------------------------------------------- /tests/workspace-proc-macro-lto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "acro" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | procm = { path = "./procm" } 8 | 9 | [workspace] 10 | # FIXME: "." is required. 11 | members = ["procm", "."] 12 | 13 | [profile.release] 14 | lto = "thin" 15 | -------------------------------------------------------------------------------- /tests/workspace-proc-macro-lto/procm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | -------------------------------------------------------------------------------- /tests/workspace-proc-macro-lto/procm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro] 4 | pub fn acro(input: TokenStream) -> TokenStream { 5 | format!(r#"fn main() {{ println!({}) }}"#, input).parse().unwrap() 6 | } 7 | -------------------------------------------------------------------------------- /tests/workspace-proc-macro-lto/src/lib.rs: -------------------------------------------------------------------------------- 1 | procm::acro!("Hello, world!"); 2 | -------------------------------------------------------------------------------- /tests/workspace-virtual/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bar" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "foo" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "bar", 14 | ] 15 | -------------------------------------------------------------------------------- /tests/workspace-virtual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ "./crates/*" ] 3 | exclude = [ "./crates/./exc" ] 4 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/bar/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bar" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/bar/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn world() -> &'static str { 2 | "world" 3 | } 4 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/exc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "exc" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/exc/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/foo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "foo" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bar = { path = "../bar" } 8 | -------------------------------------------------------------------------------- /tests/workspace-virtual/crates/foo/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, {}!", bar::world()); 3 | } 4 | -------------------------------------------------------------------------------- /toml2json/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "itoa" 7 | version = "1.0.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 10 | 11 | [[package]] 12 | name = "ryu" 13 | version = "1.0.11" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 16 | 17 | [[package]] 18 | name = "serde" 19 | version = "1.0.144" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" 22 | 23 | [[package]] 24 | name = "serde_json" 25 | version = "1.0.85" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 28 | dependencies = [ 29 | "itoa", 30 | "ryu", 31 | "serde", 32 | ] 33 | 34 | [[package]] 35 | name = "toml" 36 | version = "0.5.9" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 39 | dependencies = [ 40 | "serde", 41 | ] 42 | 43 | [[package]] 44 | name = "toml2json" 45 | version = "1.0.0" 46 | dependencies = [ 47 | "serde_json", 48 | "toml", 49 | ] 50 | -------------------------------------------------------------------------------- /toml2json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toml2json" 3 | version = "1.0.0" 4 | rust-version = "1.36" 5 | 6 | [dependencies] 7 | serde_json = "1.0.0" 8 | toml = "0.5" 9 | -------------------------------------------------------------------------------- /toml2json/README.md: -------------------------------------------------------------------------------- 1 | ## toml2json: minimal impl for toml -> json 2 | 3 | This utility program is a dependency of every rust crate derivation. 4 | Thus it's is designed to be simple and have minimal dependencies, instead of using `remarshal` which 5 | pulls in tons of python packages. 6 | -------------------------------------------------------------------------------- /toml2json/default.nix: -------------------------------------------------------------------------------- 1 | { stdenv, fetchurl, rustc }: 2 | let 3 | fetch = name: version: sha256: 4 | fetchurl { 5 | name = "crate-${name}-${version}.tar.gz"; 6 | url = "https://crates.io/api/v1/crates/${name}/${version}/download"; 7 | inherit sha256; 8 | }; 9 | 10 | manifest = builtins.fromTOML (builtins.readFile ./Cargo.toml); 11 | lock = builtins.fromTOML (builtins.readFile ./Cargo.lock); 12 | 13 | in stdenv.mkDerivation { 14 | pname = manifest.package.name; 15 | version = manifest.package.version; 16 | 17 | srcs = map ({ name, version, checksum ? null, ... }: if checksum != null then fetch name version checksum else null) lock.package; 18 | 19 | sourceRoot = "."; 20 | 21 | nativeBuildInputs = [ rustc ]; 22 | 23 | buildPhase = '' 24 | buildFlagsArray+=( 25 | --color=always 26 | --out-dir . 27 | -L . 28 | -C codegen-units=1 29 | -C opt-level=3 30 | --cap-lints allow 31 | ) 32 | 33 | run() { 34 | echo "rustc $* ''${buildFlagsArray[*]}" 35 | rustc "$@" "''${buildFlagsArray[@]}" 36 | } 37 | 38 | run itoa-*/src/lib.rs --crate-name itoa --crate-type lib \ 39 | --cfg 'feature="default"' --cfg 'feature="std"' 40 | run ryu-*/src/lib.rs --crate-name ryu --crate-type lib 41 | run serde-*/src/lib.rs --crate-name serde --crate-type lib \ 42 | --cfg 'feature="default"' --cfg 'feature="std"' 43 | run serde_json-*/src/lib.rs --crate-name serde_json --crate-type lib \ 44 | --edition=2018 \ 45 | --cfg 'feature="default"' --cfg 'feature="std"' \ 46 | --extern itoa=libitoa.rlib \ 47 | --extern ryu=libryu.rlib \ 48 | --extern serde=libserde.rlib 49 | run toml-*/src/lib.rs --crate-name toml --crate-type lib \ 50 | --edition=2018 \ 51 | --extern serde=libserde.rlib 52 | run ${./src/main.rs} --crate-name toml2json --crate-type bin \ 53 | --extern serde_json=./libserde_json.rlib \ 54 | --extern toml=libtoml.rlib 55 | ''; 56 | 57 | testToml = '' 58 | [hello] 59 | world = "good" 60 | [target."cfg(target = \"good\")"] 61 | foo = "bar" 62 | ''; 63 | 64 | testJson = ''{"hello":{"world":"good"},"target":{"cfg(target = \"good\")":{"foo":"bar"}}}''; 65 | 66 | doCheck = true; 67 | checkPhase = '' 68 | ./toml2json <<<"$testToml" >out.json 69 | echo "Got : $(cat out.json)" 70 | echo "Expect: $testJson" 71 | [[ "$(cat out.json)" == "$testJson" ]] 72 | ''; 73 | 74 | installPhase = '' 75 | mkdir -p $out/bin 76 | cp -t $out/bin ./toml2json 77 | ''; 78 | } 79 | -------------------------------------------------------------------------------- /toml2json/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdin, stdout, Read, Write}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let mut input = Vec::new(); 5 | stdin().lock().read_to_end(&mut input)?; 6 | let data: serde_json::Value = toml::from_slice(&input)?; 7 | let mut output = serde_json::to_vec(&data)?; 8 | output.push(b'\n'); 9 | stdout().lock().write_all(&output)?; 10 | Ok(()) 11 | } 12 | --------------------------------------------------------------------------------