feat(paths): init new core module 'paths'

This is to improve package location finding at eval time and script execution time.

Deprecates options lock.{repoRoot,lockFileRel} as well as option eval-cache.{repoRoot, cacheFileRel}

Instead the user must now set:
  - paths.projectRoot: pointing to the repoRoot
  - paths.package: pointing to the package directory

- paths.projectRootFile can be used to define a marker file to find the project root. The default is '.git' but it could be set to 'flake.nix' for example
- lock files are by default put in the same directory as the package definition
This commit is contained in:
DavHau 2023-09-03 01:31:05 +02:00
parent 39885acca7
commit 87722db970
13 changed files with 224 additions and 74 deletions

View File

@ -1,4 +1,4 @@
{config, ...}: {
lock.repoRoot = ./.;
lock.lockFileRel = "/locks/${config.name}.json";
paths.projectRoot = ./.;
paths.package = "/packages/${config.name}";
}

View File

@ -0,0 +1,33 @@
{lib, ...}:
with lib; {
options = {
assertions = mkOption {
type = types.listOf types.unspecified;
internal = true;
default = [];
example = [
{
assertion = false;
message = "you can't enable this for that reason";
}
];
description = lib.mdDoc ''
This option allows modules to express conditions that must
hold for the evaluation of the system configuration to
succeed, along with associated error messages for the user.
'';
};
warnings = mkOption {
internal = true;
default = [];
type = types.listOf types.str;
example = ["The `foo' service is deprecated and will go away soon!"];
description = lib.mdDoc ''
This option allows modules to show warnings to users during
the evaluation of the system configuration.
'';
};
};
# impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
}

View File

@ -1,11 +1,13 @@
{
imports = [
./assertions.nix
./docs
./deps
./env
./eval-cache
./flags
./lock
./paths
./public
./ui
];

View File

@ -46,17 +46,17 @@
# LOAD
file = cfg.repoRoot + cfg.cacheFileRel;
file = config.paths.cacheFileAbs;
refreshCommand =
l.unsafeDiscardStringContext
"cat $(nix-build ${cfg.newFile.drvPath} --no-link) > $(git rev-parse --show-toplevel)/${cfg.cacheFileRel}";
"cat $(nix-build ${cfg.newFile.drvPath} --no-link) > $(realpath $(${config.paths.findRoot})/${config.paths.package}/${config.paths.cacheFile})";
newFileMsg = "To generate a new cache file, execute:\n ${refreshCommand}";
ifdInfoMsg = "Information on how to fix this is shown below if evaluated with `--allow-import-from-derivation`";
cacheMissingMsg = "The cache file ${cfg.cacheFileRel} for drv-parts module '${packageName}' doesn't exist, please create it.";
cacheMissingMsg = "The cache file ${config.paths.package}/${config.paths.cacheFile} for drv-parts module '${packageName}' doesn't exist, please create it.";
cacheMissingError =
l.trace ''
@ -69,7 +69,7 @@
${newFileMsg}
'';
cacheInvalidMsg = "The cache file ${cfg.cacheFileRel} for drv-parts module '${packageName}' is outdated, please update it.";
cacheInvalidMsg = "The cache file ${config.paths.package}/${config.paths.cacheFile} for drv-parts module '${packageName}' is outdated, please update it.";
cacheInvalidError =
l.trace ''
@ -112,15 +112,16 @@
eval-cache.content = loadedContent;
deps = {nixpkgs, ...}: {
inherit
(nixpkgs)
jq
runCommand
writeText
writeScript
;
};
deps = {nixpkgs, ...}:
lib.mapAttrs (_: lib.mkOptionDefault) {
inherit
(nixpkgs)
jq
runCommand
writeText
writeScript
;
};
};
configIfDisabled = l.mkIf (! cfg.enable) {

View File

@ -7,23 +7,6 @@
t = l.types;
in {
options.eval-cache = {
# GLOBAL OPTIONS
repoRoot = l.mkOption {
type = t.path;
description = "The root of the current repo. Eg. 'self' in a flake";
example = lib.literalExpression ''
self
'';
};
cacheFileRel = l.mkOption {
type = t.str;
description = "Location of the cache file relative to the repoRoot";
example = lib.literalExpression ''
/rel/path/to/my/package/cache.json
'';
};
# LOCAL OPTIONS
enable = l.mkEnableOption "the evaluation cache for this derivation";
@ -33,7 +16,7 @@ in {
};
};
invalidationFields = l.mkOption rec {
invalidationFields = l.mkOption {
type = t.attrsOf t.anything;
description = "Fields, when changed, require refreshing the cache";
default = {};
@ -42,7 +25,7 @@ in {
};
};
fields = l.mkOption rec {
fields = l.mkOption {
type = t.attrsOf t.anything;
description = "Fields for which to cache evaluation";
default = {};

View File

@ -7,7 +7,7 @@
cfg = config.lock;
# LOAD
file = cfg.repoRoot + cfg.lockFileRel;
file = config.paths.lockFileAbs;
data = l.fromJSON (l.readFile file);
fileExists = l.pathExists file;
@ -23,10 +23,10 @@
from pathlib import Path
repo_path = Path(subprocess.run(
['git', 'rev-parse', '--show-toplevel'],
check=True, text=True, capture_output=True)
.stdout.strip())
lock_path_rel = Path('${cfg.lockFileRel}') # noqa: E501
['${config.paths.findRoot}'], # noqa: E501
check=True, text=True, capture_output=True
).stdout.strip())
lock_path_rel = Path('${config.paths.package}/${config.paths.lockFile}') # noqa: E501
lock_path = repo_path / lock_path_rel.relative_to(lock_path_rel.anchor)
if lock_path.exists():
@ -45,7 +45,7 @@
['git', 'rev-parse', '--show-toplevel'],
check=True, text=True, capture_output=True)
.stdout.strip())
lock_path_rel = Path('${cfg.lockFileRel}') # noqa: E501
lock_path_rel = Path('${config.paths.package}/${config.paths.lockFile}') # noqa: E501
lock_path = repo_path / lock_path_rel.relative_to(lock_path_rel.anchor)
lock_path.parent.mkdir(parents=True, exist_ok=True)
@ -126,30 +126,30 @@
'';
errorMissingFile = ''
The lock file ${cfg.repoRoot}${cfg.lockFileRel}
The lock file ${config.paths.package}/${config.paths.lockFile}
for drv-parts module '${config.name}' is missing.
To update it using flakes:
nix run -L .#${config.name}.config.lock.refresh
To update it without flakes:
bash -c $(nix-build ${config.lock.refresh.drvPath} --no-link)/bin/refresh
To update it using flakes:
nix run -L .#${config.name}.config.lock.refresh
'';
errorOutdated = field: ''
The lock file ${cfg.repoRoot}${cfg.lockFileRel}
The lock file ${config.paths.package}/${config.paths.lockFile}
for drv-parts module '${config.name}' does not contain field `${field}`.
To update it using flakes:
nix run -L .#${config.name}.config.lock.refresh
To update it without flakes:
bash -c $(nix-build ${config.lock.refresh.drvPath} --no-link)/bin/refresh
To update it using flakes:
nix run -L .#${config.name}.config.lock.refresh
'';
fileContent =

View File

@ -6,23 +6,12 @@
l = lib // builtins;
t = l.types;
in {
imports = [
(lib.mkRemovedOptionModule ["lock" "repoRoot"] "Use paths.projectRoot instead.")
(lib.mkRemovedOptionModule ["lock" "lockFileRel"] "Use paths.package instead.")
];
options.lock = {
# GLOBAL OPTIONS
repoRoot = l.mkOption {
type = t.path;
description = "The root of the current repo. Eg. 'self' in a flake";
example = lib.literalExpression ''
self
'';
};
lockFileRel = l.mkOption {
type = t.str;
description = "Location of the cache file relative to the repoRoot";
example = lib.literalExpression ''
/rel/path/to/my/package/cache.json
'';
};
content = l.mkOption {
type = t.submodule {

View File

@ -0,0 +1,31 @@
{
config,
lib,
...
}: {
imports = [
./interface.nix
];
deps = {nixpkgs, ...}: {
python3 = nixpkgs.python3;
substituteAll = nixpkgs.substituteAll;
# runCommand = nixpkgs.runCommand;
};
paths = {
lockFileAbs =
config.paths.projectRoot + "/" + config.paths.package + "/" + config.paths.lockFile;
cacheFileAbs =
config.paths.projectRoot + "/" + config.paths.package + "/" + config.paths.cacheFile;
# - identify the root by searching for the marker config.paths.projectRootFile in the current dir and parents
# - if the marker file is not found, raise an error
findRoot = let
program = config.deps.substituteAll {
src = ./find-root.py;
projectRootFile = config.paths.projectRootFile;
python3 = config.deps.python3;
postInstall = "chmod +x $out";
};
in "${program}";
};
}

View File

@ -0,0 +1,25 @@
#!@python3@/bin/python3
# - identify the root by searching for the marker config.paths.projectRootFile in the current dir and parents
# - if the marker file is not found, raise an error
import os
def find_root():
marker = "@projectRootFile@"
path = os.getcwd()
while True:
if os.path.exists(os.path.join(path, marker)):
return path
newpath = os.path.dirname(path)
if newpath == path:
raise Exception(
f"Could not find root directory (marker file: {marker})\n"
"Ensure that paths.projectRoot and paths.projectRootFile are set correctly and you are working somewhere within the project directory."
)
path = newpath
if __name__ == "__main__":
print(find_root())

View File

@ -0,0 +1,82 @@
{
lib,
config,
...
}: {
options.paths = lib.mapAttrs (_: lib.mkOption) {
# mandatory fields
projectRoot = {
type = lib.types.path;
description = ''
Path to the root of the project on which dream2nix operates.
Must contain the marker file specified by 'paths.projectRootFile'
This helps locating lock files at evaluation time.
'';
example = lib.literalExpression "./.";
};
package = {
type = lib.types.str;
description = ''
Path to the directory containing the definition of the current package.
Relative to 'paths.projectRoot'.
This helps locating package definitions for lock & update scripts.
'';
};
# optional fields
projectRootFile = {
type = lib.types.str;
description = ''
File name to look for to determine the root of the project.
Ensure 'paths.projectRoot' contains a file named like this.
This helps locating package definitions for lock & update scripts.
'';
example = ".git";
default = ".git";
};
lockFile = {
type = lib.types.str;
description = ''
Path to the lock file of the current package.
Relative to "''${paths.projectRoot}/''${paths.package}"".
'';
default = "lock.json";
};
cacheFile = {
type = lib.types.str;
description = ''
Path to the eval cache file of the current package.
Relative to "''${paths.projectRoot}/''${paths.package}"".
'';
default = "cache.json";
};
# internal fields
lockFileAbs = {
internal = true;
type = lib.types.path;
description = ''
Absolute path to the lock file of the current package.
Derived from 'paths.projectRoot', 'paths.package' and 'paths.lockFile'.
'';
};
cacheFileAbs = {
internal = true;
type = lib.types.path;
description = ''
Absolute path to the eval cache file of the current package.
Derived from 'paths.projectRoot', 'paths.package' and 'paths.cacheFile'.
'';
};
findRoot = {
internal = true;
type = lib.types.str;
description = ''
Executable script to find the package definition of the current package.
'';
};
};
}

View File

@ -18,11 +18,13 @@
}: let
l = lib // builtins;
packages = lib.filterAttrs (name: _: lib.hasPrefix "example-package-" name) self'.checks;
scripts =
l.flatten
(l.mapAttrsToList
(name: pkg: pkg.config.lock.refresh or [])
self'.packages);
packages);
update-locks =
config.writers.writePureShellScript

View File

@ -25,8 +25,7 @@
# A module imported into every package setting up the eval cache
setup = {config, ...}: {
lock.repoRoot = self;
eval-cache.repoRoot = self;
paths.projectRoot = self;
eval-cache.enable = true;
deps.npm = inputs.nixpkgs.legacyPackages.${system}.nodejs.pkgs.npm.override (old: rec {
version = "8.19.4";
@ -59,7 +58,10 @@
flip mapAttrs' examples
(name: _: {
name = "example-package-${name}";
value = examplesPath + "/${name}";
value = {
module = examplesPath + "/${name}";
packagePath = "/examples/packages/${dirName}/${name}";
};
});
allExamples = mapAttrsToList (dirName: _: readExamples dirName) examplePackagesDirs;
@ -72,11 +74,10 @@
allModules = flip mapAttrs' allModules' (name: module: {
inherit name;
value = [
module
module.module
setup
{
lock.lockFileRel = "/locks/${name}/lock-${system}.json";
eval-cache.cacheFileRel = "/locks/${name}/cache-${system}.json";
paths.package = module.packagePath;
}
];
});

View File

@ -7,12 +7,13 @@ let
# TODO: modify this according to your repo structure
setupModule = {config, ...}: {
# Define the root of your repo. All other paths are relative to it.
lock.repoRoot = ./.;
lock.projectRoot = ./.;
# define how a specific lock file should be called
# This definition will produce lock files like:
# my-package-x86_64-linux-lock.json
lock.lockFileRel = "/${config.name}-${config.deps.stdenv.system}-lock.json";
paths.package = ".";
paths.lockFile = "/${config.name}-${config.deps.stdenv.system}-lock.json";
};
# evaluate package module