2021-04-12 15:52:51 +02:00
|
|
|
|
# Copyright (c) 2019 Vincent Ambo
|
|
|
|
|
# Copyright (c) 2020-2021 The TVL Authors
|
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
#
|
2023-11-30 14:27:59 +01:00
|
|
|
|
# Provides a function to automatically read a filesystem structure
|
2021-04-12 15:52:51 +02:00
|
|
|
|
# into a Nix attribute set.
|
2021-08-26 18:41:50 +02:00
|
|
|
|
#
|
2021-09-08 17:16:11 +02:00
|
|
|
|
# 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.
|
|
|
|
|
{ ... }:
|
2019-12-21 06:42:32 +01:00
|
|
|
|
|
2019-11-15 15:52:23 +01:00
|
|
|
|
let
|
|
|
|
|
inherit (builtins)
|
|
|
|
|
attrNames
|
2021-11-23 13:00:27 +01:00
|
|
|
|
concatMap
|
2020-08-27 02:50:33 +02:00
|
|
|
|
concatStringsSep
|
2021-11-23 12:24:58 +01:00
|
|
|
|
elem
|
|
|
|
|
elemAt
|
2019-11-15 15:52:23 +01:00
|
|
|
|
filter
|
2019-12-19 16:33:30 +01:00
|
|
|
|
hasAttr
|
2019-11-15 15:52:23 +01:00
|
|
|
|
head
|
2020-05-28 14:51:36 +02:00
|
|
|
|
isAttrs
|
2019-11-15 15:52:23 +01:00
|
|
|
|
listToAttrs
|
|
|
|
|
map
|
|
|
|
|
match
|
2020-05-28 14:51:36 +02:00
|
|
|
|
readDir
|
|
|
|
|
substring;
|
2019-11-15 15:52:23 +01:00
|
|
|
|
|
2021-01-30 06:58:54 +01:00
|
|
|
|
argsWithPath = args: parts:
|
2020-08-04 10:50:10 +02:00
|
|
|
|
let meta.locatedAt = parts;
|
|
|
|
|
in meta // (if isAttrs args then args else args meta);
|
2019-11-15 15:52:23 +01:00
|
|
|
|
|
2020-05-28 14:51:36 +02:00
|
|
|
|
readDirVisible = path:
|
|
|
|
|
let
|
|
|
|
|
children = readDir path;
|
2023-02-28 11:41:17 +01:00
|
|
|
|
# skip hidden files, except for those that contain special instructions to readTree
|
|
|
|
|
isVisible = f: f == ".skip-subtree" || f == ".skip-tree" || (substring 0 1 f) != ".";
|
2020-05-28 14:51:36 +02:00
|
|
|
|
names = filter isVisible (attrNames children);
|
|
|
|
|
in
|
|
|
|
|
listToAttrs (map
|
|
|
|
|
(name: {
|
|
|
|
|
inherit name;
|
|
|
|
|
value = children.${name};
|
|
|
|
|
})
|
|
|
|
|
names);
|
|
|
|
|
|
2021-09-15 13:22:54 +02:00
|
|
|
|
# Create a mark containing the location of this attribute and
|
|
|
|
|
# a list of all child attribute names added by readTree.
|
|
|
|
|
marker = parts: children: {
|
2020-08-27 02:50:33 +02:00
|
|
|
|
__readTree = parts;
|
2021-09-15 13:22:54 +02:00
|
|
|
|
__readTreeChildren = builtins.attrNames children;
|
2020-08-27 02:50:33 +02:00
|
|
|
|
};
|
|
|
|
|
|
2022-02-07 11:24:18 +01:00
|
|
|
|
# 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;
|
|
|
|
|
|
2022-02-02 11:19:04 +01:00
|
|
|
|
# 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;
|
|
|
|
|
|
2021-09-15 13:22:54 +02:00
|
|
|
|
# Import a file and enforce our calling convention
|
|
|
|
|
importFile = args: scopedArgs: path: parts: filter:
|
2021-09-08 17:27:37 +02:00
|
|
|
|
let
|
|
|
|
|
importedFile =
|
2022-10-10 04:04:37 +02:00
|
|
|
|
if scopedArgs != { } && builtins ? scopedImport # For tvix
|
2021-09-08 17:27:37 +02:00
|
|
|
|
then builtins.scopedImport scopedArgs path
|
|
|
|
|
else import path;
|
2021-01-30 09:25:05 +01:00
|
|
|
|
pathType = builtins.typeOf importedFile;
|
2021-09-15 13:22:54 +02:00
|
|
|
|
in
|
|
|
|
|
if pathType != "lambda"
|
2023-02-28 11:41:17 +01:00
|
|
|
|
then throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }"
|
2021-11-13 18:33:34 +01:00
|
|
|
|
else importedFile (filter parts (argsWithPath args parts));
|
2019-11-15 15:52:23 +01:00
|
|
|
|
|
2019-12-09 21:34:49 +01:00
|
|
|
|
nixFileName = file:
|
2021-02-17 23:48:54 +01:00
|
|
|
|
let res = match "(.*)\\.nix" file;
|
2019-11-15 15:52:23 +01:00
|
|
|
|
in if res == null then null else head res;
|
|
|
|
|
|
2023-02-28 11:41:17 +01:00
|
|
|
|
# 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 }:
|
2019-12-09 04:16:02 +01:00
|
|
|
|
let
|
2021-01-30 06:58:54 +01:00
|
|
|
|
dir = readDirVisible initPath;
|
2023-02-28 11:41:17 +01:00
|
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
|
2021-01-30 06:58:54 +01:00
|
|
|
|
joinChild = c: initPath + ("/" + c);
|
2019-12-09 21:34:49 +01:00
|
|
|
|
|
2021-04-12 21:49:36 +02:00
|
|
|
|
self =
|
|
|
|
|
if rootDir
|
|
|
|
|
then { __readTree = [ ]; }
|
2021-09-15 13:22:54 +02:00
|
|
|
|
else importFile args scopedArgs initPath parts argsFilter;
|
2021-04-12 21:49:36 +02:00
|
|
|
|
|
2023-02-28 11:41:17 +01:00
|
|
|
|
# Import subdirectories of the current one, unless any skip
|
|
|
|
|
# instructions exist.
|
2019-12-19 16:33:30 +01:00
|
|
|
|
#
|
|
|
|
|
# This file can optionally contain information on why the tree
|
|
|
|
|
# should be ignored, but its content is not inspected by
|
|
|
|
|
# readTree
|
2019-12-09 21:34:49 +01:00
|
|
|
|
filterDir = f: dir."${f}" == "directory";
|
2023-02-28 11:41:17 +01:00
|
|
|
|
filteredChildren = map
|
2019-12-19 16:33:30 +01:00
|
|
|
|
(c: {
|
2019-12-09 21:34:49 +01:00
|
|
|
|
name = c;
|
2023-02-28 11:41:17 +01:00
|
|
|
|
value = readTreeImpl {
|
2021-09-08 17:27:37 +02:00
|
|
|
|
inherit argsFilter scopedArgs;
|
2021-04-12 21:49:36 +02:00
|
|
|
|
args = args;
|
|
|
|
|
initPath = (joinChild c);
|
|
|
|
|
rootDir = false;
|
|
|
|
|
parts = (parts ++ [ c ]);
|
|
|
|
|
};
|
2019-12-09 21:34:49 +01:00
|
|
|
|
})
|
|
|
|
|
(filter filterDir (attrNames dir));
|
|
|
|
|
|
2023-02-28 11:41:17 +01:00
|
|
|
|
# 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);
|
|
|
|
|
|
2019-12-09 21:34:49 +01:00
|
|
|
|
# Import Nix files
|
2021-12-07 10:54:50 +01:00
|
|
|
|
nixFiles =
|
2023-02-28 11:41:17 +01:00
|
|
|
|
if skipSubtree then [ ]
|
2021-12-07 10:54:50 +01:00
|
|
|
|
else filter (f: f != null) (map nixFileName (attrNames dir));
|
2021-09-15 13:22:54 +02:00
|
|
|
|
nixChildren = map
|
|
|
|
|
(c:
|
|
|
|
|
let
|
|
|
|
|
p = joinChild (c + ".nix");
|
|
|
|
|
childParts = parts ++ [ c ];
|
|
|
|
|
imported = importFile args scopedArgs p childParts argsFilter;
|
|
|
|
|
in
|
|
|
|
|
{
|
2019-12-09 21:34:49 +01:00
|
|
|
|
name = c;
|
2021-09-15 13:22:54 +02:00
|
|
|
|
value =
|
|
|
|
|
if isAttrs imported
|
2022-02-02 11:19:04 +01:00
|
|
|
|
then merge imported (marker childParts { })
|
2021-09-15 13:22:54 +02:00
|
|
|
|
else imported;
|
2019-12-09 21:34:49 +01:00
|
|
|
|
})
|
|
|
|
|
nixFiles;
|
2022-01-30 17:06:58 +01:00
|
|
|
|
|
2021-09-15 13:22:54 +02:00
|
|
|
|
nodeValue = if dir ? "default.nix" then self else { };
|
|
|
|
|
|
|
|
|
|
allChildren = listToAttrs (
|
|
|
|
|
if dir ? "default.nix"
|
|
|
|
|
then children
|
|
|
|
|
else nixChildren ++ children
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
in
|
2023-02-28 11:41:17 +01:00
|
|
|
|
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;
|
2021-01-30 06:58:54 +01:00
|
|
|
|
|
2022-02-07 11:46:00 +01:00
|
|
|
|
# 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;
|
|
|
|
|
|
2021-12-16 22:06:22 +01:00
|
|
|
|
# Function which can be used to find all readTree targets within an
|
2021-11-23 13:00:27 +01:00
|
|
|
|
# attribute set.
|
|
|
|
|
#
|
|
|
|
|
# This function will gather physical targets, that is targets which
|
|
|
|
|
# correspond directly to a location in the repository, as well as
|
2022-02-07 11:46:00 +01:00
|
|
|
|
# subtargets (specified in the meta.ci.targets attribute of a node).
|
2021-11-23 13:00:27 +01:00
|
|
|
|
#
|
|
|
|
|
# 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;
|
|
|
|
|
})
|
2022-02-07 11:46:00 +01:00
|
|
|
|
(subtargets node))
|
2021-11-23 13:00:27 +01:00
|
|
|
|
else [ ];
|
2021-11-23 14:31:17 +01:00
|
|
|
|
|
|
|
|
|
# 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";
|
2021-01-30 06:58:54 +01:00
|
|
|
|
in
|
|
|
|
|
{
|
2022-02-07 11:24:18 +01:00
|
|
|
|
inherit gather mkLabel;
|
2021-11-23 13:00:27 +01:00
|
|
|
|
|
2021-09-08 17:16:11 +02:00
|
|
|
|
__functor = _:
|
|
|
|
|
{ path
|
|
|
|
|
, args
|
2021-11-13 18:33:34 +01:00
|
|
|
|
, filter ? (_parts: x: x)
|
2021-09-08 17:27:37 +02:00
|
|
|
|
, scopedArgs ? { }
|
|
|
|
|
}:
|
2021-09-08 17:16:11 +02:00
|
|
|
|
readTree {
|
2021-09-08 17:27:37 +02:00
|
|
|
|
inherit args scopedArgs;
|
2021-09-08 17:16:11 +02:00
|
|
|
|
argsFilter = filter;
|
|
|
|
|
initPath = path;
|
|
|
|
|
rootDir = true;
|
|
|
|
|
parts = [ ];
|
|
|
|
|
};
|
2021-11-23 12:24:58 +01:00
|
|
|
|
|
|
|
|
|
# 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}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
2021-11-23 12:39:20 +01:00
|
|
|
|
|
|
|
|
|
# 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;
|
2021-11-23 14:31:17 +01:00
|
|
|
|
|
2022-02-07 11:46:00 +01:00
|
|
|
|
# Takes an attribute set and adds a meta.ci.targets attribute to it
|
2021-11-23 14:31:17 +01:00
|
|
|
|
# which contains all direct children of the attribute set which are
|
|
|
|
|
# derivations.
|
|
|
|
|
#
|
|
|
|
|
# Type: attrs -> attrs
|
2022-02-07 11:46:00 +01:00
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2021-01-30 06:58:54 +01:00
|
|
|
|
}
|