This change adds a new attribute to readTree nodes, `__readTreeChildren` which is a list of attribute names added to this node by readTree. This is then used by `gather` for `ci.targets` to avoid evaluating attributes unnecessarily. Especially since Nix is not as lazy as we'd like when determining types (i. e. child ? __readTree needs to force `child` even when it's not an attribute set), evaluating attributes unnecessarily is sometimes problematic. Change-Id: I0a98691d41f987e23ee7e9ba21fbe465da5fe402
		
			
				
	
	
		
			141 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
	
		
			4.1 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
 | ||
|     baseNameOf
 | ||
|     concatStringsSep
 | ||
|     filter
 | ||
|     hasAttr
 | ||
|     head
 | ||
|     isAttrs
 | ||
|     length
 | ||
|     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;
 | ||
|   };
 | ||
| 
 | ||
|   # 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 (argsWithPath args parts) 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 = 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 parts {}
 | ||
|           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 nodeValue // allChildren // (marker parts allChildren)
 | ||
|       else nodeValue;
 | ||
| 
 | ||
| in {
 | ||
|   __functor = _:
 | ||
|     { path
 | ||
|     , args
 | ||
|     , filter ? (x: _parts: x)
 | ||
|     , scopedArgs ? {} }:
 | ||
|       readTree {
 | ||
|         inherit args scopedArgs;
 | ||
|         argsFilter = filter;
 | ||
|         initPath = path;
 | ||
|         rootDir = true;
 | ||
|         parts = [];
 | ||
|       };
 | ||
| }
 |