diff options
Diffstat (limited to 'nix/readTree/default.nix')
-rw-r--r-- | nix/readTree/default.nix | 247 |
1 files changed, 171 insertions, 76 deletions
diff --git a/nix/readTree/default.nix b/nix/readTree/default.nix index 259f2f2fbf..4a745ce33c 100644 --- a/nix/readTree/default.nix +++ b/nix/readTree/default.nix @@ -2,7 +2,7 @@ # Copyright (c) 2020-2021 The TVL Authors # SPDX-License-Identifier: MIT # -# Provides a function to automatically read a a filesystem structure +# Provides a function to automatically read a filesystem structure # into a Nix attribute set. # # Called with an attribute set taking the following arguments: @@ -41,12 +41,16 @@ let readDirVisible = path: let children = readDir path; - isVisible = f: f == ".skip-subtree" || (substring 0 1 f) != "."; + # skip hidden files, except for those that contain special instructions to readTree + isVisible = f: f == ".skip-subtree" || f == ".skip-tree" || (substring 0 1 f) != "."; names = filter isVisible (attrNames children); - in listToAttrs (map (name: { - inherit name; - value = children.${name}; - }) names); + in + listToAttrs (map + (name: { + inherit name; + value = children.${name}; + }) + names); # Create a mark containing the location of this attribute and # a list of all child attribute names added by readTree. @@ -55,66 +59,121 @@ let __readTreeChildren = builtins.attrNames children; }; + # Create a label from a target's tree location. + mkLabel = target: + let label = concatStringsSep "/" target.__readTree; + in if target ? __subtarget + then "${label}:${target.__subtarget}" + else label; + + # Merge two attribute sets, but place attributes in `passthru` via + # `overrideAttrs` for derivation targets that support it. + merge = a: b: + if a ? overrideAttrs + then + a.overrideAttrs + (prev: { + passthru = (prev.passthru or { }) // b; + }) + else a // b; + # Import a file and enforce our calling convention importFile = args: scopedArgs: path: parts: filter: - let - importedFile = if scopedArgs != {} - then builtins.scopedImport scopedArgs path - else import path; + let + importedFile = + if scopedArgs != { } && builtins ? scopedImport # For tvix + then builtins.scopedImport scopedArgs path + else import path; pathType = builtins.typeOf importedFile; - in + in if pathType != "lambda" - then builtins.throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }" + then throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }" else importedFile (filter parts (argsWithPath args parts)); nixFileName = file: let res = match "(.*)\\.nix" file; in if res == null then null else head res; - readTree = { args, initPath, rootDir, parts, argsFilter, scopedArgs }: + # Internal implementation of readTree, which handles things like the + # skipping of trees and subtrees. + # + # This method returns an attribute sets with either of two shapes: + # + # { ok = ...; } # a tree was read successfully + # { skip = true; } # a tree was skipped + # + # The higher-level `readTree` method assembles the final attribute + # set out of these results at the top-level, and the internal + # `children` implementation unwraps and processes nested trees. + readTreeImpl = { args, initPath, rootDir, parts, argsFilter, scopedArgs }: let dir = readDirVisible initPath; + + # Determine whether any part of this tree should be skipped. + # + # Adding a `.skip-subtree` file will still allow the import of + # the current node's "default.nix" file, but stop recursion + # there. + # + # Adding a `.skip-tree` file will completely ignore the folder + # in which this file is located. + skipTree = hasAttr ".skip-tree" dir; + skipSubtree = skipTree || hasAttr ".skip-subtree" dir; + joinChild = c: initPath + ("/" + c); - self = if rootDir - then { __readTree = []; } + self = + if rootDir + then { __readTree = [ ]; } else importFile args scopedArgs initPath parts argsFilter; - # Import subdirectories of the current one, unless the special - # `.skip-subtree` file exists which makes readTree ignore the - # children. + # Import subdirectories of the current one, unless any skip + # instructions exist. # # This file can optionally contain information on why the tree # should be ignored, but its content is not inspected by # readTree filterDir = f: dir."${f}" == "directory"; - children = if hasAttr ".skip-subtree" dir then [] else map (c: { - name = c; - value = readTree { - inherit argsFilter scopedArgs; - args = args; - initPath = (joinChild c); - rootDir = false; - parts = (parts ++ [ c ]); - }; - }) (filter filterDir (attrNames dir)); + filteredChildren = map + (c: { + name = c; + value = readTreeImpl { + inherit argsFilter scopedArgs; + args = args; + initPath = (joinChild c); + rootDir = false; + parts = (parts ++ [ c ]); + }; + }) + (filter filterDir (attrNames dir)); + + # Remove skipped children from the final set, and unwrap the + # result set. + children = + if skipSubtree then [ ] + else map ({ name, value }: { inherit name; value = value.ok; }) (filter (child: child.value ? ok) filteredChildren); # Import Nix files - nixFiles = if hasAttr ".skip-subtree" dir then [] + nixFiles = + if skipSubtree then [ ] else filter (f: f != null) (map nixFileName (attrNames dir)); - nixChildren = map (c: let - p = joinChild (c + ".nix"); - childParts = parts ++ [ c ]; - imported = importFile args scopedArgs p childParts argsFilter; - in { - name = c; - value = - if isAttrs imported - then imported // marker childParts {} - else imported; - }) nixFiles; - - nodeValue = if dir ? "default.nix" then self else {}; + nixChildren = map + (c: + let + p = joinChild (c + ".nix"); + childParts = parts ++ [ c ]; + imported = importFile args scopedArgs p childParts argsFilter; + in + { + name = c; + value = + if isAttrs imported + then merge imported (marker childParts { }) + else imported; + }) + nixFiles; + + nodeValue = if dir ? "default.nix" then self else { }; allChildren = listToAttrs ( if dir ? "default.nix" @@ -123,16 +182,45 @@ let ); in - if isAttrs nodeValue - then nodeValue // allChildren // (marker parts allChildren) - else nodeValue; + if skipTree + then { skip = true; } + else { + ok = + if isAttrs nodeValue + then merge nodeValue (allChildren // (marker parts allChildren)) + else nodeValue; + }; + + # Top-level implementation of readTree itself. + readTree = args: + let + tree = readTreeImpl args; + in + if tree ? skip + then throw "Top-level folder has a .skip-tree marker and could not be read by readTree!" + else tree.ok; + + # Helper function to fetch subtargets from a target. This is a + # temporary helper to warn on the use of the `meta.targets` + # attribute, which is deprecated in favour of `meta.ci.targets`. + subtargets = node: + let targets = (node.meta.targets or [ ]) ++ (node.meta.ci.targets or [ ]); + in if node ? meta.targets then + builtins.trace '' + [1;31mWarning: The meta.targets attribute is deprecated. + + Please move the subtargets of //${mkLabel node} to the + meta.ci.targets attribute. + [0m + '' + targets else targets; # Function which can be used to find all readTree targets within an # attribute set. # # This function will gather physical targets, that is targets which # correspond directly to a location in the repository, as well as - # subtargets (specified in the meta.targets attribute of a node). + # subtargets (specified in the meta.ci.targets attribute of a node). # # This can be used to discover targets for inclusion in CI # pipelines. @@ -143,40 +231,42 @@ let # should be included in the build. gather = eligible: node: if node ? __readTree then - # Include the node itself if it is eligible. - (if eligible node then [ node ] else []) + # Include the node itself if it is eligible. + (if eligible node then [ node ] else [ ]) # Include eligible children of the node ++ concatMap (gather eligible) (map (attr: node."${attr}") node.__readTreeChildren) # Include specified sub-targets of the node ++ filter eligible (map - (k: (node."${k}" or {}) // { - # Keep the same tree location, but explicitly mark this - # node as a subtarget. - __readTree = node.__readTree; - __readTreeChildren = []; - __subtarget = k; - }) - (node.meta.targets or [])) - else []; + (k: (node."${k}" or { }) // { + # Keep the same tree location, but explicitly mark this + # node as a subtarget. + __readTree = node.__readTree; + __readTreeChildren = [ ]; + __subtarget = k; + }) + (subtargets node)) + else [ ]; # Determine whether a given value is a derivation. # Copied from nixpkgs/lib for cases where lib is not available yet. isDerivation = x: isAttrs x && x ? type && x.type == "derivation"; -in { - inherit gather; +in +{ + inherit gather mkLabel; __functor = _: { path , args , filter ? (_parts: x: x) - , scopedArgs ? {} }: - readTree { - inherit args scopedArgs; - argsFilter = filter; - initPath = path; - rootDir = true; - parts = []; - }; + , scopedArgs ? { } + }: + readTree { + inherit args scopedArgs; + argsFilter = filter; + initPath = path; + rootDir = true; + parts = [ ]; + }; # In addition to readTree itself, some functionality is exposed that # is useful for users of readTree. @@ -193,7 +283,7 @@ in { # which should be able to access the restricted folder. # # reason: Textual explanation for the restriction (included in errors) - restrictFolder = { folder, exceptions ? [], reason }: parts: args: + restrictFolder = { folder, exceptions ? [ ], reason }: parts: args: if (elemAt parts 0) == folder || elem parts exceptions then args else args // { @@ -216,16 +306,21 @@ in { # It is often required to create the args attribute set. fix = f: let x = f x; in x; - # Takes an attribute set and adds a meta.targets attribute to it + # Takes an attribute set and adds a meta.ci.targets attribute to it # which contains all direct children of the attribute set which are # derivations. # # Type: attrs -> attrs - drvTargets = attrs: attrs // { - meta = { - targets = builtins.filter - (x: isDerivation attrs."${x}") - (builtins.attrNames attrs); - } // (attrs.meta or {}); - }; + drvTargets = attrs: + attrs // { + # preserve .meta from original attrs + meta = (attrs.meta or { }) // { + # preserve .meta.ci (except .targets) from original attrs + ci = (attrs.meta.ci or { }) // { + targets = builtins.filter + (x: isDerivation attrs."${x}") + (builtins.attrNames attrs); + }; + }; + }; } |