feat: add support for platform specs (#15)

This PR adds a new way of declaring supported platforms for packages. Previously individual build, host, and target values needed to be set for a given platform combination. Now, a platform spec can be written to generate many platform combinations.

This allows for easy mapping of all possible systems with `@all`, linux systems with `@linux` or any other available alias of `lib.systems.doubles.<alias>` by using `@<alias>`. In addition, you can use `@build` and `@host` to lock either the host or target respectively to match the prior platform.

Reviewed-on: #15
This commit is contained in:
Jake Hamilton 2025-09-10 12:32:11 +00:00
parent 4e4b9366a4
commit 2f31813c88
10 changed files with 236 additions and 148 deletions

View file

@ -28,7 +28,8 @@ let
};
};
new = module:
new =
module:
let
set = lib.modules.run {
modules = [
@ -37,14 +38,16 @@ let
./src/mirrors
recursiveLibModule
] ++ lib.lists.from.any module;
]
++ lib.lists.from.any module;
};
in
set.config // {
inherit new;
inherit (set) extend;
};
set.config
// {
inherit new;
inherit (set) extend;
};
in
new [
./src/packages
]
new [
./src/packages
]

View file

@ -20,9 +20,9 @@
},
"branch": "main",
"submodules": false,
"revision": "c55943c5e8197cbc2d0e506693fd5e7e3ea0a0c3",
"revision": "2af4e9f1d5dfe835c0eba15be1035bfa4065aca7",
"url": null,
"hash": "sha256-XBhIQ0XR1QabTgyo2c5wMk6xk/Bd3bCUlnTzRPcVfcw="
"hash": "sha256-fwycuhXJGDHniLlxA8Xx4IELUObrP0EW3EnsDwNdyoQ="
}
},
"version": 6

View file

@ -33,9 +33,10 @@ in
hooks.target.target
];
in
builtins.foldl' (
final: defaults: lib.dag.apply.defaults final (resolvePhases defaults)
) (resolvePhases package.phases) all;
builtins.foldl'
(final: defaults: lib.dag.apply.defaults final (resolvePhases defaults))
(resolvePhases package.phases)
all;
phases = lib.dag.apply.defaults phasesWithHooks {
unpack = lib.dag.entry.before [ "patch" ] "";
@ -91,14 +92,11 @@ in
foundation.stage2-coreutils
]
++ lib.lists.when (system == "x86_64-linux") [
(lib.packages.build packages.foundation.bash.versions."5.2.15-stage1" "i686-linux" system system)
.package
(lib.packages.build packages.foundation.coreutils.versions."9.4-stage1" "i686-linux" system system)
.package
(lib.packages.build packages.foundation.bash.versions."5.2.15-stage1" "i686-linux" system system).package
(lib.packages.build packages.foundation.coreutils.versions."9.4-stage1" "i686-linux" system system).package
]
++ lib.lists.when (system != "i686-linux" && system != "x86_64-linux") [
(lib.packages.build packages.foundation.bash.versions."5.2.15-stage1" "x86_64-linux" system system)
.package
(lib.packages.build packages.foundation.bash.versions."5.2.15-stage1" "x86_64-linux" system system).package
(lib.packages.build packages.foundation.coreutils.versions."9.4-stage1" "x86_64-linux" system
system
).package

View file

@ -6,22 +6,24 @@ let
inherit (config.internal.packages) foundation;
in
{
config.builders.passthrough = { config }: {
options = {
settings.derivation = lib.options.create {
description = "The derivation to use for the package.";
type = lib.types.derivation;
config.builders.passthrough =
{ config }:
{
options = {
settings.derivation = lib.options.create {
description = "The derivation to use for the package.";
type = lib.types.derivation;
};
};
config = {
build =
package:
package.builder.settings.derivation
// {
inherit package;
inherit (package) meta extend extras;
};
};
};
config = {
build =
package:
package.builder.settings.derivation
// {
inherit package;
inherit (package) meta extend extras;
};
};
};
}

View file

@ -2,8 +2,8 @@
includes = [
./options.nix
./packages.nix
./platforms.nix
./systems.nix
./types.nix
./fp.nix
];
}

View file

@ -1,14 +0,0 @@
{ lib, config }:
{
config = {
lib.fp = {
foldl =
f: default: items:
let
process =
index: if index < 0 then default else f (process (index - 1)) (builtins.elemAt items index);
in
process (builtins.length items - 1);
};
};
}

View file

@ -92,7 +92,9 @@ in
global = global.config;
};
prefix = package.__module__.args.dynamic.meta.prefix ++ [ "<context ${builtins.concatStringsSep path}>" ];
prefix = package.__module__.args.dynamic.meta.prefix ++ [
"<context ${builtins.concatStringsSep path}>"
];
modules = (builtins.map (context: { config = context; }) contexts) ++ [
{
freeform = lib.types.any;

View file

@ -0,0 +1,92 @@
{ config }:
let
inherit (config) lib;
in
{
config.lib.platforms = {
from = {
# Converts a platform spec into a list of platforms.
#
# Example specs:
#
# {
# build = "x86_64-linux";
# host = "x86_64-linux";
# target = "x86_64-linux";
# }
#
# {
# build = "*";
# host = "*";
# target = "@host";
# }
#
# {
# build = "*";
# host = ["x86_64-linux", "aarch64-linux"];
# target = "@host";
# }
#
# [
# {
# build = "*";
# host = ["x86_64-linux", "aarch64-linux"];
# target = "@host";
# }
# {
# build = "i686-linux";
# host = "@build";
# target = "@host";
# }
# ]
#
spec =
input:
let
specs = lib.lists.from.any input;
platforms = lib.lists.flatten (
builtins.map (
spec:
let
build = lib.platforms.expand spec spec.build;
host = lib.platforms.expand spec spec.host;
target = lib.platforms.expand spec spec.target;
in
builtins.concatMap (
build:
builtins.concatMap (
host:
builtins.map (target: {
build = build;
host = if host == "@build" then build else host;
target =
if target == "@host" then
host
else if target == "@build" then
build
else
target;
}) target
) host
) build
) specs
);
in
platforms;
};
expand =
spec: value:
if builtins.isList value then
builtins.concatMap (system: lib.platforms.expand spec system) value
else if value == "@build" then
lib.platforms.expand spec spec.build
else if value == "@build" || value == "@host" then
[ value ]
else if lib.strings.hasPrefix "@" value then
lib.systems.doubles.${lib.strings.removePrefix "@" value}
else
[ value ];
};
}

View file

@ -25,7 +25,8 @@ let
lib.attrs.match patterns;
getDoubles =
predicate: builtins.map lib.systems.into.double (builtins.filter predicate lib.systems.doubles.all);
predicate:
builtins.map lib.systems.into.double (builtins.filter predicate lib.systems.doubles.resolved);
in
{
config = {
@ -2788,11 +2789,7 @@ in
// (builtins.removeAttrs settings [ "system" ]);
assertions = builtins.foldl' (
result:
{ assertion, message }:
if assertion resolved then
result
else builtins.throw message
result: { assertion, message }: if assertion resolved then result else builtins.throw message
) true (resolved.system.abi.assertions or [ ]);
in
assert resolved.useAndroidPrebuilt -> resolved.isAndroid;

View file

@ -172,84 +172,91 @@ in
result.config;
};
submodule = { config, meta, name ? "builder" }: {
options = {
__modules__ = lib.options.create {
description = "User specified modules for the builder definition.";
type = lib.types.list.of (initial // { merge = lib.options.merge.one; });
internal = true;
default.value = [ ];
};
extend = lib.options.create {
description = "Extend the package definition.";
type = lib.types.function lib.types.raw;
default.value =
module:
let
normalized =
if builtins.isList module then
module
else if builtins.isFunction module || module ? config then
[ module ]
else
[
{
config = module;
}
];
modules = config.__modules__ ++ normalized;
result = lib.modules.run {
prefix = meta.prefix;
args = {
global = global.config;
};
modules = [
submodule
{ config.__modules__ = modules; }
]
++ modules;
};
in
result.config;
};
name = lib.options.create {
description = "The name of the builder.";
type = lib.types.string;
default = lib.attrs.when (name != "builder") {
value = name;
submodule =
{
config,
meta,
name ? "builder",
}:
{
options = {
__modules__ = lib.options.create {
description = "User specified modules for the builder definition.";
type = lib.types.list.of (initial // { merge = lib.options.merge.one; });
internal = true;
default.value = [ ];
};
writable = false;
};
build = lib.options.create {
description = "The build function which takes a package definition and creates a derivation.";
type = lib.types.function lib.types.artifact;
};
extend = lib.options.create {
description = "Extend the package definition.";
type = lib.types.function lib.types.raw;
default.value =
module:
let
normalized =
if builtins.isList module then
module
else if builtins.isFunction module || module ? config then
[ module ]
else
[
{
config = module;
}
];
settings = lib.options.create {
description = "The settings for the builder.";
type = lib.types.submodule ({ config }: {});
default.value = { };
modules = config.__modules__ ++ normalized;
result = lib.modules.run {
prefix = meta.prefix;
args = {
global = global.config;
};
modules = [
submodule
{ config.__modules__ = modules; }
]
++ modules;
};
in
result.config;
};
name = lib.options.create {
description = "The name of the builder.";
type = lib.types.string;
default = lib.attrs.when (name != "builder") {
value = name;
};
writable = false;
};
build = lib.options.create {
description = "The build function which takes a package definition and creates a derivation.";
type = lib.types.function lib.types.artifact;
};
settings = lib.options.create {
description = "The settings for the builder.";
type = lib.types.submodule ({ config }: { });
default.value = { };
};
};
};
};
type = (lib.types.coerceWithLocation initial transform final) // {
name = "Builder";
description = "a builder";
};
in
type // {
children = type.children // {
inherit submodule;
};
type
// {
children = type.children // {
inherit submodule;
};
};
aliases =
let
@ -331,6 +338,28 @@ in
};
};
};
spec = lib.types.submodule (
{ config }:
{
options = {
build = lib.options.create {
description = "The build platform for the package.";
type = lib.types.string;
};
host = lib.options.create {
description = "The host platform for the package.";
type = lib.types.string;
};
target = lib.options.create {
description = "The target platform for the package.";
type = lib.types.string;
};
};
}
);
};
artifact =
@ -696,30 +725,9 @@ in
platforms = lib.options.create {
description = "The platforms that the package supports.";
type = lib.types.list.of (
lib.types.submodule (
{ config }:
{
options = {
build = lib.options.create {
description = "The build platform for the package.";
type = lib.types.string;
};
host = lib.options.create {
description = "The host platform for the package.";
type = lib.types.string;
};
target = lib.options.create {
description = "The target platform for the package.";
type = lib.types.string;
};
};
}
)
);
type = lib.types.either lib.types.platforms.spec (lib.types.list.of lib.types.platforms.spec);
default.value = [ ];
apply = spec: lib.platforms.from.spec spec;
};
platform =
@ -859,7 +867,7 @@ in
]
);
in
lib.types.either type (lib.types.attrs.of type);
lib.types.either type (lib.types.attrs.of type);
default.value = null;
};