summary refs log tree commit diff
path: root/maintainers/scripts/update.nix
blob: 7435cf64425c02ef28f69e1b8263e310c36aa29c (plain) (blame)
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
{ package ? null
, maintainer ? null
, predicate ? null
, path ? null
, max-workers ? null
, include-overlays ? false
, keep-going ? null
, commit ? null
}:

# TODO: add assert statements

let
  pkgs = import ./../../default.nix (
    if include-overlays == false then
      { overlays = []; }
    else if include-overlays == true then
      { } # Let Nixpkgs include overlays impurely.
    else { overlays = include-overlays; }
  );

  inherit (pkgs) lib;

  /* Remove duplicate elements from the list based on some extracted value. O(n^2) complexity.
   */
  nubOn = f: list:
    if list == [] then
      []
    else
      let
        x = lib.head list;
        xs = lib.filter (p: f x != f p) (lib.drop 1 list);
      in
        [x] ++ nubOn f xs;

  /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate.

    Type: packagesWithPath :: AttrPath → (AttrPath → derivation → bool) → AttrSet → List<AttrSet{attrPath :: str; package :: derivation; }>
          AttrPath :: [str]

    The packages will be returned as a list of named pairs comprising of:
      - attrPath: stringified attribute path (based on `rootPath`)
      - package: corresponding derivation
   */
  packagesWithPath = rootPath: cond: pkgs:
    let
      packagesWithPathInner = path: pathContent:
        let
          result = builtins.tryEval pathContent;

          dedupResults = lst: nubOn ({ package, attrPath }: package.updateScript) (lib.concatLists lst);
        in
          if result.success then
            let
              evaluatedPathContent = result.value;
            in
              if lib.isDerivation evaluatedPathContent then
                lib.optional (cond path evaluatedPathContent) { attrPath = lib.concatStringsSep "." path; package = evaluatedPathContent; }
              else if lib.isAttrs evaluatedPathContent then
                # If user explicitly points to an attrSet or it is marked for recursion, we recur.
                if path == rootPath || evaluatedPathContent.recurseForDerivations or false || evaluatedPathContent.recurseForRelease or false then
                  dedupResults (lib.mapAttrsToList (name: elem: packagesWithPathInner (path ++ [name]) elem) evaluatedPathContent)
                else []
              else []
          else [];
    in
      packagesWithPathInner rootPath pkgs;

  /* Recursively find all packages (derivations) in `pkgs` matching `cond` predicate.
   */
  packagesWith = packagesWithPath [];

  /* Recursively find all packages in `pkgs` with updateScript matching given predicate.
   */
  packagesWithUpdateScriptMatchingPredicate = cond:
    packagesWith (path: pkg: builtins.hasAttr "updateScript" pkg && cond path pkg);

  /* Recursively find all packages in `pkgs` with updateScript by given maintainer.
   */
  packagesWithUpdateScriptAndMaintainer = maintainer':
    let
      maintainer =
        if ! builtins.hasAttr maintainer' lib.maintainers then
          builtins.throw "Maintainer with name `${maintainer'} does not exist in `maintainers/maintainer-list.nix`."
        else
          builtins.getAttr maintainer' lib.maintainers;
    in
      packagesWithUpdateScriptMatchingPredicate (path: pkg:
                         (if builtins.hasAttr "maintainers" pkg.meta
                           then (if builtins.isList pkg.meta.maintainers
                                   then builtins.elem maintainer pkg.meta.maintainers
                                   else maintainer == pkg.meta.maintainers
                                )
                           else false
                         )
                   );

  /* Recursively find all packages under `path` in `pkgs` with updateScript.
   */
  packagesWithUpdateScript = path: pkgs:
    let
      prefix = lib.splitString "." path;
      pathContent = lib.attrByPath prefix null pkgs;
    in
      if pathContent == null then
        builtins.throw "Attribute path `${path}` does not exists."
      else
        packagesWithPath prefix (path: pkg: builtins.hasAttr "updateScript" pkg)
                       pathContent;

  /* Find a package under `path` in `pkgs` and require that it has an updateScript.
   */
  packageByName = path: pkgs:
    let
        package = lib.attrByPath (lib.splitString "." path) null pkgs;
    in
      if package == null then
        builtins.throw "Package with an attribute name `${path}` does not exists."
      else if ! builtins.hasAttr "updateScript" package then
        builtins.throw "Package with an attribute name `${path}` does not have a `passthru.updateScript` attribute defined."
      else
        { attrPath = path; inherit package; };

  /* List of packages matched based on the CLI arguments.
   */
  packages =
    if package != null then
      [ (packageByName package pkgs) ]
    else if predicate != null then
      packagesWithUpdateScriptMatchingPredicate predicate pkgs
    else if maintainer != null then
      packagesWithUpdateScriptAndMaintainer maintainer pkgs
    else if path != null then
      packagesWithUpdateScript path pkgs
    else
      builtins.throw "No arguments provided.\n\n${helpText}";

  helpText = ''
    Please run:

        % nix-shell maintainers/scripts/update.nix --argstr maintainer garbas

    to run all update scripts for all packages that lists \`garbas\` as a maintainer
    and have \`updateScript\` defined, or:

        % nix-shell maintainers/scripts/update.nix --argstr package gnome.nautilus

    to run update script for specific package, or

        % nix-shell maintainers/scripts/update.nix --arg predicate '(path: pkg: builtins.isList pkg.updateScript && builtins.length pkg.updateScript >= 1 && (let script = builtins.head pkg.updateScript; in builtins.isAttrs script && script.name == "gnome-update-script"))'

    to run update script for all packages matching given predicate, or

        % nix-shell maintainers/scripts/update.nix --argstr path gnome

    to run update script for all package under an attribute path.

    You can also add

        --argstr max-workers 8

    to increase the number of jobs in parallel, or

        --argstr keep-going true

    to continue running when a single update fails.

    You can also make the updater automatically commit on your behalf from updateScripts
    that support it by adding

        --argstr commit true
  '';

  /* Transform a matched package into an object for update.py.
   */
  packageData = { package, attrPath }: {
    name = package.name;
    pname = lib.getName package;
    oldVersion = lib.getVersion package;
    updateScript = map builtins.toString (lib.toList (package.updateScript.command or package.updateScript));
    supportedFeatures = package.updateScript.supportedFeatures or [];
    attrPath = package.updateScript.attrPath or attrPath;
  };

  /* JSON file with data for update.py.
   */
  packagesJson = pkgs.writeText "packages.json" (builtins.toJSON (map packageData packages));

  optionalArgs =
    lib.optional (max-workers != null) "--max-workers=${max-workers}"
    ++ lib.optional (keep-going == "true") "--keep-going"
    ++ lib.optional (commit == "true") "--commit";

  args = [ packagesJson ] ++ optionalArgs;

in pkgs.stdenv.mkDerivation {
  name = "nixpkgs-update-script";
  buildCommand = ''
    echo ""
    echo "----------------------------------------------------------------"
    echo ""
    echo "Not possible to update packages using \`nix-build\`"
    echo ""
    echo "${helpText}"
    echo "----------------------------------------------------------------"
    exit 1
  '';
  shellHook = ''
    unset shellHook # do not contaminate nested shells
    exec ${pkgs.python3.interpreter} ${./update.py} ${builtins.concatStringsSep " " args}
  '';
}