summaryrefslogtreecommitdiff
path: root/ci/eval/diff.nix
blob: bb8ba8088c7fff4e1c68169cd9f8c0edb4d14a69 (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
100
101
102
103
104
105
106
107
108
109
110
111
{
  lib,
  runCommand,
  writeText,
}:

{
  beforeDir,
  afterDir,
  evalSystem,
}:

let
  # Usually we expect a derivation, but when evaluating in multiple separate steps, we pass
  # nix store paths around. These need to be turned into (fake) derivations again to track
  # dependencies properly.
  # We use two steps for evaluation, because we compare results from two different checkouts.
  # CI additionalls spreads evaluation across multiple workers.
  before = if lib.isDerivation beforeDir then beforeDir else lib.toDerivation beforeDir;
  after = if lib.isDerivation afterDir then afterDir else lib.toDerivation afterDir;

  /*
    Computes the key difference between two attrs

    {
      added: [ <keys only in the second object> ],
      removed: [ <keys only in the first object> ],
      changed: [ <keys with different values between the two objects> ],
      rebuilds: [ <keys in the second object with values not present at all in first object> ],
    }
  */
  diff =
    old: new:
    let
      filterKeys = cond: attrs: lib.attrNames (lib.filterAttrs cond attrs);
      oldOutputs = lib.pipe old [
        (lib.mapAttrsToList (_: lib.attrValues))
        lib.concatLists
        (lib.flip lib.genAttrs (_: true))
      ];
    in
    {
      added = filterKeys (n: _: !(old ? ${n})) new;
      removed = filterKeys (n: _: !(new ? ${n})) old;
      changed = filterKeys (
        n: v:
        # Filter out attributes that don't exist anymore
        (new ? ${n})

        # Filter out attributes that are the same as the new value
        && (v != (new.${n}))
      ) old;
      # A "rebuild" is every attrpath ...
      rebuilds = filterKeys (
        _: pkg:
        # ... that has at least one output ...
        lib.any (
          output:
          # ... which has not been built in "old" already.
          !(oldOutputs ? ${output})
        ) (lib.attrValues pkg)
      ) new;
    };

  getAttrs =
    dir:
    let
      raw = builtins.readFile "${dir}/${evalSystem}/paths.json";
      # The file contains Nix paths; we need to ignore them for evaluation purposes,
      # else there will be a "is not allowed to refer to a store path" error.
      data = builtins.unsafeDiscardStringContext raw;
    in
    builtins.fromJSON data;

  beforeAttrs = getAttrs before;
  afterAttrs = getAttrs after;
  diffAttrs = diff beforeAttrs afterAttrs;
  diffJson = writeText "diff.json" (builtins.toJSON diffAttrs);

  # The maintainer list is not diffed, but just taken as is, to provide a map
  # of maintainers on the target branch. A list of GitHub IDs is sufficient for
  # all our purposes and reduces size massively.
  meta = lib.importJSON "${after}/${evalSystem}/meta.json";
  maintainers = lib.pipe meta [
    (lib.mapAttrsToList (
      k: v: {
        # splits off the platform suffix
        package = lib.pipe k [
          (lib.splitString ".")
          lib.init
          (lib.concatStringsSep ".")
        ];
        maintainers = map (m: m.githubId) v.maintainers or [ ];
      }
    ))
    # Some paths don't have a platform suffix, those will appear with an empty package here.
    (lib.filter ({ package, maintainers }: package != "" && maintainers != [ ]))
  ];
  maintainersJson = writeText "maintainers.json" (builtins.toJSON maintainers);
in
runCommand "diff" { } ''
  mkdir -p $out/${evalSystem}

  cp -r --no-preserve=mode ${before} $out/before
  cp -r --no-preserve=mode ${after} $out/after
  # JSON files will be processed above explicitly, so avoid copying over
  # the source files to keep the artifacts smaller.
  find $out/before $out/after -iname '*.json' -delete
  cp ${diffJson} $out/${evalSystem}/diff.json
  cp ${maintainersJson} $out/${evalSystem}/maintainers.json
''