Derivations that support overrideAttrs now have their readTree markers merged in using it, as passthru attributes. This makes the significant difference that overriding readTree targets using `overrideAttrs` keeps their readTree data intact. Change-Id: Ieef635f048781bf4782c1a28532b89a66d9ca24d Reviewed-on: https://cl.tvl.fyi/c/depot/+/5186 Tested-by: BuildkiteCI Reviewed-by: ezemtsov <eugene.zemtsov@gmail.com> Autosubmit: tazjin <tazjin@tvl.su>
		
			
				
	
	
		
			257 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| # Copyright (c) 2019 Vincent Ambo
 | ||
| # Copyright (c) 2020-2021 The TVL Authors
 | ||
| # SPDX-License-Identifier: MIT
 | ||
| #
 | ||
| # Provides a function to automatically read a a filesystem structure
 | ||
| # into a Nix attribute set.
 | ||
| #
 | ||
| # Called with an attribute set taking the following arguments:
 | ||
| #
 | ||
| #   path: Path to a directory from which to start reading the tree.
 | ||
| #
 | ||
| #   args: Argument set to pass to each imported file.
 | ||
| #
 | ||
| #   filter: Function to filter `args` based on the tree location. This should
 | ||
| #           be a function of the form `args -> location -> args`, where the
 | ||
| #           location is a list of strings representing the path components of
 | ||
| #           the current readTree target. Optional.
 | ||
| { ... }:
 | ||
| 
 | ||
| let
 | ||
|   inherit (builtins)
 | ||
|     attrNames
 | ||
|     concatMap
 | ||
|     concatStringsSep
 | ||
|     elem
 | ||
|     elemAt
 | ||
|     filter
 | ||
|     hasAttr
 | ||
|     head
 | ||
|     isAttrs
 | ||
|     listToAttrs
 | ||
|     map
 | ||
|     match
 | ||
|     readDir
 | ||
|     substring;
 | ||
| 
 | ||
|   argsWithPath = args: parts:
 | ||
|     let meta.locatedAt = parts;
 | ||
|     in meta // (if isAttrs args then args else args meta);
 | ||
| 
 | ||
|   readDirVisible = path:
 | ||
|     let
 | ||
|       children = readDir path;
 | ||
|       isVisible = f: f == ".skip-subtree" || (substring 0 1 f) != ".";
 | ||
|       names = filter isVisible (attrNames children);
 | ||
|     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.
 | ||
|   marker = parts: children: {
 | ||
|     __readTree = parts;
 | ||
|     __readTreeChildren = builtins.attrNames children;
 | ||
|   };
 | ||
| 
 | ||
|   # 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;
 | ||
|       pathType = builtins.typeOf importedFile;
 | ||
|     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, ... }"
 | ||
|     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 }:
 | ||
|     let
 | ||
|       dir = readDirVisible initPath;
 | ||
|       joinChild = c: initPath + ("/" + c);
 | ||
| 
 | ||
|       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.
 | ||
|       #
 | ||
|       # 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));
 | ||
| 
 | ||
|       # Import Nix files
 | ||
|       nixFiles =
 | ||
|         if hasAttr ".skip-subtree" dir 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 merge imported (marker childParts { })
 | ||
|               else imported;
 | ||
|           })
 | ||
|         nixFiles;
 | ||
| 
 | ||
|       nodeValue = if dir ? "default.nix" then self else { };
 | ||
| 
 | ||
|       allChildren = listToAttrs (
 | ||
|         if dir ? "default.nix"
 | ||
|         then children
 | ||
|         else nixChildren ++ children
 | ||
|       );
 | ||
| 
 | ||
|     in
 | ||
|     if isAttrs nodeValue
 | ||
|     then merge nodeValue (allChildren // (marker parts allChildren))
 | ||
|     else nodeValue;
 | ||
| 
 | ||
|   # 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).
 | ||
|   #
 | ||
|   # This can be used to discover targets for inclusion in CI
 | ||
|   # pipelines.
 | ||
|   #
 | ||
|   # Called with the arguments:
 | ||
|   #
 | ||
|   #   eligible: Function to determine whether the given derivation
 | ||
|   #             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 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 [ ];
 | ||
| 
 | ||
|   # 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;
 | ||
| 
 | ||
|   __functor = _:
 | ||
|     { path
 | ||
|     , args
 | ||
|     , filter ? (_parts: x: x)
 | ||
|     , 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.
 | ||
| 
 | ||
|   # Create a readTree filter disallowing access to the specified
 | ||
|   # top-level folder in the repository, except for specific exceptions
 | ||
|   # specified by their (full) paths.
 | ||
|   #
 | ||
|   # Called with the arguments:
 | ||
|   #
 | ||
|   #   folder: Name of the restricted top-level folder (e.g. 'experimental')
 | ||
|   #
 | ||
|   #   exceptions: List of readTree parts (e.g. [ [ "services" "some-app" ] ]),
 | ||
|   #               which should be able to access the restricted folder.
 | ||
|   #
 | ||
|   #   reason: Textual explanation for the restriction (included in errors)
 | ||
|   restrictFolder = { folder, exceptions ? [ ], reason }: parts: args:
 | ||
|     if (elemAt parts 0) == folder || elem parts exceptions
 | ||
|     then args
 | ||
|     else args // {
 | ||
|       depot = args.depot // {
 | ||
|         "${folder}" = throw ''
 | ||
|           Access to targets under //${folder} is not permitted from
 | ||
|           other repository paths. Specific exceptions are configured
 | ||
|           at the top-level.
 | ||
| 
 | ||
|           ${reason}
 | ||
|           At location: ${builtins.concatStringsSep "." parts}
 | ||
|         '';
 | ||
|       };
 | ||
|     };
 | ||
| 
 | ||
|   # This definition of fix is identical to <nixpkgs>.lib.fix, but is
 | ||
|   # provided here for cases where readTree is used before nixpkgs can
 | ||
|   # be imported.
 | ||
|   #
 | ||
|   # 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
 | ||
|   # 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 { });
 | ||
|   };
 | ||
| }
 |