feat: simple builder

This commit is contained in:
Jake Hamilton 2024-06-14 07:01:18 -07:00
parent b3f9fe574e
commit 3713635d76
Signed by untrusted user: jakehamilton
GPG key ID: 9762169A1B35EA68
8 changed files with 354 additions and 34 deletions

View file

@ -8,10 +8,10 @@
},
"locked": {
"dir": "foundation",
"dirtyRev": "cdc90a46565dadea3b3c8f673b8b55d4ff75ea92-dirty",
"dirtyShortRev": "cdc90a4-dirty",
"lastModified": 1718356339,
"narHash": "sha256-BSiyT1q5xSlO1DyNJlkRWI82WWxVtQ1Xy8zPOqdqUlU=",
"dirtyRev": "b3f9fe574e6ff545e4a6fe6ad5e90fc71baeece3-dirty",
"dirtyShortRev": "b3f9fe5-dirty",
"lastModified": 1718366115,
"narHash": "sha256-/ktNtlY/usvE1YaathJ0f6y60cv3gQjc32SMKApsp7Y=",
"type": "git",
"url": "file:../?dir=foundation"
},
@ -24,10 +24,10 @@
"lib": {
"locked": {
"dir": "lib",
"dirtyRev": "cdc90a46565dadea3b3c8f673b8b55d4ff75ea92-dirty",
"dirtyShortRev": "cdc90a4-dirty",
"lastModified": 1718356339,
"narHash": "sha256-BSiyT1q5xSlO1DyNJlkRWI82WWxVtQ1Xy8zPOqdqUlU=",
"dirtyRev": "b3f9fe574e6ff545e4a6fe6ad5e90fc71baeece3-dirty",
"dirtyShortRev": "b3f9fe5-dirty",
"lastModified": 1718366115,
"narHash": "sha256-/ktNtlY/usvE1YaathJ0f6y60cv3gQjc32SMKApsp7Y=",
"type": "git",
"url": "file:../?dir=lib"
},

View file

@ -0,0 +1,73 @@
{
lib,
config,
}: let
cfg = config.builders.basic;
lib' = config.lib;
inherit (config) foundation;
in {
config.builders = {
basic = {
executable = "${foundation.stage2-bash}/bin/bash";
build = package: let
phases = package.phases;
sorted = lib.dag.sort.topographic phases;
script =
lib.strings.concatMapSep "\n" (
entry:
if builtins.isFunction entry.value
then entry.value package
else entry.value
)
sorted.result;
system = package.platform.build;
deps = lib'.packages.dependencies.resolve package.deps system;
in
(builtins.trace script)
builtins.derivation (package.env
// {
inherit (package) name;
inherit script system;
passAsFile = ["script"];
SHELL = cfg.executable;
PATH = lib.paths.bin (
(lib'.packages.dependencies.getPackages "build.host" deps)
++ [
foundation.stage2-bash
foundation.stage2-coreutils
]
);
builder = cfg.executable;
args = [
"-e"
(builtins.toFile "bash-builder.sh" ''
export CONFIG_SHELL=$SHELL
# Normalize the NIX_BUILD_CORES variable. The value might be 0, which
# means that we're supposed to try and auto-detect the number of
# available CPU cores at run-time.
NIX_BUILD_CORES="''${NIX_BUILD_CORES:-1}"
if ((NIX_BUILD_CORES <= 0)); then
guess=$(nproc 2>/dev/null || true)
((NIX_BUILD_CORES = guess <= 0 ? 1 : guess))
fi
export NIX_BUILD_CORES
bash -eux $scriptPath
'')
];
});
};
};
}

View file

@ -0,0 +1,18 @@
{
lib,
config,
}: let
lib' = config.lib;
in {
includes = [
./basic.nix
];
options = {
builders = lib.options.create {
description = "A set of builders that can be used to build packages.";
type = lib.types.attrs.of lib'.types.builder;
default.value = {};
};
};
}

View file

@ -4,11 +4,16 @@
lib,
config,
}: let
lib' = config.lib;
in {
config = {
exports = {
lib = config.lib;
modules = import ./modules.nix;
packages = {
example = lib'.packages.export "example.x";
};
};
};
}

View file

@ -6,6 +6,47 @@
in {
config = {
lib.packages = {
dependencies = {
getPackages = path: dependencies: let
resolved =
if builtins.isList path
then path
else lib.strings.split "." path;
attrs = lib.attrs.select resolved {} dependencies;
in
lib.attrs.mapToList (name: value: value.package) attrs;
resolve = dependencies: system:
builtins.mapAttrs
# Note that this does not correspond to the "host" and "target" platforms, but rather
# where the code is used and where it is intended to end up.
(
host: platforms:
builtins.mapAttrs
(
target: deps:
builtins.mapAttrs
(
name: dependency:
assert lib.errors.trace (dependency.namespace != null) "Namespace unknown for dependency `${name}`."; let
targeted = lib'.packages.export [dependency.namespace name] system;
in
if targeted == null
then builtins.throw "Dependency `${dependency.namespace}.${name}` does not support system `${system}`."
else targeted
)
deps
)
platforms
)
dependencies;
};
## Get a package from the package set by its path. The path can be either
## a string or a list of strings that is used to access `config.packages.generic`.
##
## @type String | List String -> Package
get = path: let
resolved =
if builtins.isList path
@ -14,12 +55,16 @@ in {
package = lib.attrs.selectOrThrow resolved config.packages.generic;
in
assert lib.errors.trace (builtins.length resolved > 1) "Packages must have a namespace specified.";
assert lib.errors.trace (builtins.length resolved > 1) "Cannot get package without a namespace.";
package
// {
namespace = lib.modules.override 99 (builtins.head resolved);
};
## Export a package by its path. Use this function with the `config.exports.packages.*`
## options.
##
## @type String | List String -> String -> Package
export = path: system: let
resolved =
if builtins.isList path

View file

@ -11,40 +11,40 @@ in {
options = {
name = {
full = lib.options.create {
type = lib.types.string;
description = "The full name of the license.";
type = lib.types.string;
};
short = lib.options.create {
type = lib.types.string;
description = "The short name of the license.";
type = lib.types.string;
};
};
spdx = lib.options.create {
description = "The SPDX identifier for the license.";
type = lib.types.nullish lib.types.string;
default.value = null;
description = "The SPDX identifier for the license.";
};
url = lib.options.create {
type = lib.types.nullish lib.types.string;
description = "The URL for the license.";
type = lib.types.nullish lib.types.string;
};
free = lib.options.create {
description = "Whether the license is free.";
type = lib.types.bool;
default.value = true;
description = "Whether the license is free.";
};
redistributable = lib.options.create {
description = "Whether the license is allows redistribution.";
type = lib.types.bool;
default = {
text = "config.free";
value = config.free;
};
description = "Whether the license is allows redistribution.";
};
};
});
@ -54,51 +54,51 @@ in {
meta = lib.types.submodule {
options = {
description = lib.options.create {
description = "The description for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
description = "The description for the package.";
};
homepage = lib.options.create {
description = "The homepage for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
description = "The homepage for the package.";
};
license = lib.options.create {
description = "The license for the package.";
type = lib.types.nullish config.lib.types.license;
default.value = null;
description = "The license for the package.";
};
free = lib.options.create {
description = "Whether the package is free.";
type = lib.types.bool;
default.value = true;
description = "Whether the package is free.";
};
insecure = lib.options.create {
description = "Whether the package is insecure.";
type = lib.types.bool;
default.value = false;
description = "Whether the package is insecure.";
};
broken = lib.options.create {
description = "Whether the package is broken.";
type = lib.types.bool;
default.value = false;
description = "Whether the package is broken.";
};
main = lib.options.create {
description = "The main entry point for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
description = "The main entry point for the package.";
};
platforms = lib.options.create {
description = "The platforms the package supports.";
type = lib.types.list.of lib.types.string;
default.value = [];
description = "The platforms the package supports.";
};
};
};
@ -108,6 +108,7 @@ in {
options = {
build = lib.options.create {
description = "The build function which takes a package definition and creates a derivation.";
type = lib.types.function lib.types.derivation;
};
};
@ -120,6 +121,12 @@ in {
]);
}));
versioned = lib.types.attrs.of (lib.types.submodule ({name}: {
freeform = lib.types.attrs.of (lib.types.submodule [
lib'.types.package.versioned'
]);
}));
# packages.<system>
targeted = lib.types.attrs.of (lib.types.submodule ({name}: let
system = name;
@ -148,6 +155,7 @@ in {
# packages.<system>.<cross>
options.cross = lib.attrs.generate lib'.systems.doubles.all (cross:
lib.options.create {
description = "A cross-compiling package set.";
default.value = {};
# packages.<system>.<cross>.<namespace>
type = lib.types.attrs.of (lib.types.submodule (
@ -177,7 +185,6 @@ in {
dependencies = {
generic = lib.types.attrs.of (lib.types.nullish lib'.types.package.generic);
targeted = lib.types.attrs.of (lib.types.nullish lib'.types.package.targeted);
};
@ -188,13 +195,17 @@ in {
name ? assert false; null,
config,
}: {
freeform = lib.types.any;
options = {
namespace = lib.options.create {
description = "The namespace for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
pname = lib.options.create {
description = "The program name for the package";
type = lib.types.string;
default = {
text = "<name> or \"unknown\"";
@ -206,6 +217,139 @@ in {
};
version = lib.options.create {
description = "The version for the package.";
type = lib.types.nullish lib.types.version;
default.value = null;
};
meta = lib.options.create {
description = "Metadata for the package.";
type = lib'.types.meta;
default.value = {
name = config.pname;
};
};
phases = lib.options.create {
description = "The phases for the package.";
type = lib.types.dag.of (
lib.types.either
lib.types.string
(lib.types.function lib.types.string)
);
default.value = {};
};
env = lib.options.create {
description = "The environment for the package.";
type = lib.types.attrs.of lib.types.string;
default.value = {};
};
builder = lib.options.create {
description = "The builder for the package.";
type = lib'.types.builder;
};
deps = {
build = {
only = lib.options.create {
description = "Dependencies which are only used in the build environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
build = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the build environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
};
host = {
only = lib.options.create {
description = "Dependencies which are only used in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are executed in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the host environment which produces code for the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
};
target = {
only = lib.options.create {
description = "Dependencies which are only used in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
};
};
versions = lib.options.create {
description = "Available package versions.";
type = lib.types.attrs.of lib'.types.package.versioned;
default.value = {};
};
};
};
versioned = lib.types.submodule lib'.types.package.versioned';
versioned' = args @ {
name ? assert false; null,
config,
}: {
freeform = lib.types.any;
options = {
namespace = lib.options.create {
description = "The namespace for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
pname = lib.options.create {
description = "The program name for the package";
type = lib.types.string;
default = {
text = "<name> or \"unknown\"";
value =
if args ? name
then args.name
else "unknown";
};
};
version = lib.options.create {
description = "The version for the package.";
type = lib.types.nullish lib.types.version;
default.value = null;
};
@ -218,6 +362,7 @@ in {
};
phases = lib.options.create {
description = "The phases for the package.";
type = lib.types.dag.of (
lib.types.either
lib.types.string
@ -227,32 +372,38 @@ in {
};
env = lib.options.create {
description = "The environment for the package.";
type = lib.types.attrs.of lib.types.string;
default.value = {};
};
builder = lib.options.create {
description = "The builder for the package.";
type = lib'.types.builder;
};
deps = {
build = {
only = lib.options.create {
description = "Dependencies which are only used in the build environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
build = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the build environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
@ -260,16 +411,19 @@ in {
host = {
only = lib.options.create {
description = "Dependencies which are only used in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are executed in the host environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the host environment which produces code for the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
@ -277,11 +431,13 @@ in {
target = {
only = lib.options.create {
description = "Dependencies which are only used in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the target environment.";
type = lib'.types.dependencies.generic;
default.value = {};
};
@ -289,7 +445,8 @@ in {
};
versions = lib.options.create {
type = lib.types.attrs.of lib'.types.packages.generic;
description = "Available package versions.";
type = lib.types.attrs.of lib'.types.package.versioned;
default.value = {};
};
};
@ -302,7 +459,10 @@ in {
config,
}: {
options = {
freeform = lib.types.any;
name = lib.options.create {
description = "The name of the package.";
type = lib.types.string;
default = {
text = "\${namespace}-\${pname}-\${version} or \${pname}-\${version}";
@ -320,11 +480,13 @@ in {
};
namespace = lib.options.create {
description = "The namespace for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
pname = lib.options.create {
description = "The program name for the package.";
type = lib.types.string;
default = {
text = "<name> or \"unknown\"";
@ -336,11 +498,13 @@ in {
};
version = lib.options.create {
description = "The version for the package.";
type = lib.types.nullish lib.types.version;
default.value = null;
};
meta = lib.options.create {
description = "Metadata for the package.";
type = lib'.types.meta;
default.value = {
name = config.pname;
@ -349,22 +513,26 @@ in {
platform = {
build = lib.options.create {
description = "The build platform for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
host = lib.options.create {
description = "The host platform for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
target = lib.options.create {
description = "The target platform for the package.";
type = lib.types.nullish lib.types.string;
default.value = null;
};
};
phases = lib.options.create {
description = "Build phases for the package.";
type = lib.types.dag.of (
lib.types.either
lib.types.string
@ -374,36 +542,43 @@ in {
};
env = lib.options.create {
description = "The environment for the package.";
type = lib.types.attrs.of lib.types.string;
default.value = {};
};
builder = lib.options.create {
description = "The builder for the package.";
type = lib'.types.builder;
};
package = lib.options.create {
description = "The built derivation.";
type = lib.types.derivation;
};
deps = {
build = {
only = lib.options.create {
description = "Dependencies which are only used in the build environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
build = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the build environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the host environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are created in the build environment and are executed in the target environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
@ -411,16 +586,19 @@ in {
host = {
only = lib.options.create {
description = "Dependencies which are only used in the host environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
host = lib.options.create {
description = "Dependencies which are executed in the host environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the host environment which produces code for the target environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
@ -428,11 +606,13 @@ in {
target = {
only = lib.options.create {
description = "Dependencies which are only used in the target environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
target = lib.options.create {
description = "Dependencies which are executed in the target environment.";
type = lib'.types.dependencies.targeted;
default.value = {};
};
@ -440,7 +620,8 @@ in {
};
versions = lib.options.create {
type = lib.types.attrs.of lib'.types.packages.targeted;
description = "Available package versions.";
type = lib.types.attrs.of lib'.types.package.versioned;
default.value = {};
};
};

View file

@ -1,4 +1,5 @@
{
builders = ./builders;
exports = ./exports;
lib = ./lib;
packages = ./packages;

View file

@ -6,8 +6,6 @@
lib' = config.lib;
in {
config = {
exports.packages.example-x = lib'.packages.export "example.x";
packages = {
generic = {
example = {
@ -15,12 +13,7 @@ in {
meta.platforms = ["i686-linux" "x86_64-linux"];
version = "1.0.0";
builder.build = package:
derivation {
name = package.name;
builder = "/bin/sh";
system = package.platform.build;
};
builder = config.builders.basic;
phases = {
build = package: ''
@ -31,6 +24,10 @@ in {
make install DESTDIR=$out
'';
};
versions = {
"latest" = config.packages.generic.example.x;
};
};
};
};