summaryrefslogtreecommitdiffstats
path: root/pkgs/rust/lib/crate2nix-package.nix
blob: ee750961f2fe4a4a6ab29c21e4e3508a220758de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# Per-crate Rust builds with crate2nix.
#
# resolvedJson selects how the build plan is obtained:
#   null (default)  <src>/Cargo.json if present, else IFD from <src>/Cargo.lock
#   a path          that file (crate2nix generate --format json)
#   false           always IFD from <src>/Cargo.lock
#
# Overrides are attrs, or functions from the crate's buildRustCrate arguments
# to attrs, and are merged into those arguments before buildRustCrate sees
# them, so they can also set release.  For a workspace member,
# workspaceOverride applies first and crateOverrides.<name> sees its result.
# nixpkgs' defaultCrateOverrides still apply inside buildRustCrate.

{ pkgs
, lib ? pkgs.lib
, applyOverride

, name
, src

# crate name -> attrs or (args -> attrs)
, crateOverrides ? {}

# attrs or (args -> attrs), for every workspace member
, workspaceOverride ? {}

# IFD only; Cargo.json has its features baked in
, features ? [ "default" ]

# Cargo.lock the JSON plan must match; null: no check
, cargoLock ? null

, resolvedJson ? null
}:

let
  resolvedJsonFromSrc = src + "/Cargo.json";

  useJson =
    if resolvedJson == null then builtins.pathExists resolvedJsonFromSrc
    else resolvedJson != false;

  resolvedJson' =
    if resolvedJson != null then resolvedJson else resolvedJsonFromSrc;

  workspaceMembers = builtins.attrNames cargoNix.workspaceMembers;

  buildRustCrateForPkgs = pkgs: args:
    let
      withWorkspace =
        if builtins.elem args.crateName workspaceMembers
        then applyOverride workspaceOverride args
        else args;
    in
    pkgs.buildRustCrate
      (applyOverride (crateOverrides.${args.crateName} or {}) withWorkspace);

  fromJson = pkgs.crate2nix.build-from-json {
    inherit buildRustCrateForPkgs pkgs src;
    resolvedJson = resolvedJson';
  };

  fromLock =
    pkgs.callPackage
      (pkgs.crate2nix.tools.generatedCargoNix {
        inherit name src;
        additionalCargoNixArgs = [ "--all-features" ];
      })
      {
        rootFeatures = features;
        inherit buildRustCrateForPkgs;
      };

  # Fails evaluation if the plan uses a crate version the lock doesn't have,
  # i.e. Cargo.lock changed since Cargo.json was generated. Dependencies added
  # to the lock aren't detected, since the plan may leave out crates for
  # features or targets it doesn't use; those fail the build instead.
  checkLock = result:
    let
      id = name: version: "${name} ${version}";
      lockPackages = (builtins.fromTOML (builtins.readFile cargoLock)).package;
      locked = builtins.listToAttrs (map
        (p: { name = id p.name p.version; value = null; })
        (builtins.filter (p: p ? source) lockPackages));
      planned = lib.mapAttrsToList (_: c: id c.crateName c.version)
        (lib.filterAttrs (_: c: (c.source.type or "local") != "local")
          (builtins.fromJSON (builtins.readFile resolvedJson')).crates);
      missing = builtins.filter (i: !(locked ? ${i})) planned;
    in
    if cargoLock == null || missing == [] then result
    else throw "crate2nix-package: ${toString resolvedJson'} is stale against ${toString cargoLock}; not in the lock: ${lib.concatStringsSep ", " (lib.take 5 missing)}${lib.optionalString (builtins.length missing > 5) ", ..."}";

  cargoNix = if useJson then checkLock fromJson else fromLock;
in
cargoNix // {
  build =
    if cargoNix.rootCrate or null != null then cargoNix.rootCrate.build
    else cargoNix.allWorkspaceMembers;
}