feat: package extend, dynamic propagation

This commit is contained in:
Jake Hamilton 2024-07-09 02:54:33 -07:00
parent 3f9d287065
commit 7d94b7f665
Signed by untrusted user: jakehamilton
GPG key ID: 9762169A1B35EA68
9 changed files with 251 additions and 134 deletions

View file

@ -49,18 +49,18 @@ lib: {
## ##
## @type Attrs -> Attrs ## @type Attrs -> Attrs
create = create =
settings@{ settings@{ name
name, , description ? name
description ? name, , fallback ? { }
fallback ? { }, , check ? lib.fp.const true
check ? lib.fp.const true, , merge ? lib.options.merge.default
merge ? lib.options.merge.default, , functor ? lib.types.functor name
functor ? lib.types.functor name, , mergeType ? lib.types.merge functor
mergeType ? lib.types.merge functor, , getSubOptions ? lib.fp.const { }
getSubOptions ? lib.fp.const { }, , getSubModules ? null
getSubModules ? null, , withSubModules ? lib.fp.const null
withSubModules ? lib.fp.const null, , children ? { }
children ? { }, ,
}: }:
{ {
__type__ = "type"; __type__ = "type";
@ -108,7 +108,7 @@ lib: {
identifier = lib.options.getIdentifier location; identifier = lib.options.getIdentifier location;
first = builtins.elemAt definitions 0; first = builtins.elemAt definitions 0;
files = builtins.map lib.modules.getFiles definitions; files = lib.modules.getFiles definitions;
serializedFiles = builtins.concatStringsSep " and " files; serializedFiles = builtins.concatStringsSep " and " files;
getType = getType =
@ -118,13 +118,16 @@ lib: {
else else
builtins.typeOf value; builtins.typeOf value;
commonType = builtins.foldl' ( commonType = builtins.foldl'
type: definition: (
if getType definition.value != type then type: definition:
builtins.throw "The option `${identifier}` has conflicting definitions in ${files}" if getType definition.value != type then
else builtins.throw "The option `${identifier}` has conflicting definitions in ${serializedFiles}"
type else
) (getType first.value) definitions; type
)
(getType first.value)
definitions;
mergeStringifiableAttrs = lib.options.merge.one; mergeStringifiableAttrs = lib.options.merge.one;
@ -132,7 +135,7 @@ lib: {
mergeList = mergeList =
if builtins.length definitions > 1 then if builtins.length definitions > 1 then
builtins.throw "The option `${identifier}` has conflicting definitions in ${files}" builtins.throw "The option `${identifier}` has conflicting definitions in ${serializedFiles}"
else else
(lib.types.list.of lib.types.any).merge; (lib.types.list.of lib.types.any).merge;
@ -140,10 +143,12 @@ lib: {
location: definitions: x: location: definitions: x:
let let
resolvedLocation = location ++ [ "<function body>" ]; resolvedLocation = location ++ [ "<function body>" ];
resolvedDefinitions = builtins.map (definition: { resolvedDefinitions = builtins.map
__file__ = definition.__file__; (definition: {
value = definition.value x; __file__ = definition.__file__;
}) definitions; value = definition.value x;
})
definitions;
in in
lib.types.any.merge resolvedLocation resolvedDefinitions; lib.types.any.merge resolvedLocation resolvedDefinitions;
@ -200,14 +205,15 @@ lib: {
## @type Int -> Int -> Attrs ## @type Int -> Int -> Attrs
between = between =
start: end: start: end:
assert lib.errors.trace ( assert lib.errors.trace
start <= end (
) "lib.types.ints.between start must be less than or equal to end"; start <= end
lib.types.withCheck lib.types.int (value: value >= start && value <= end) ) "lib.types.ints.between start must be less than or equal to end";
// { lib.types.withCheck lib.types.int (value: value >= start && value <= end)
name = "IntBetween"; // {
description = "integer between ${description start end}"; name = "IntBetween";
}; description = "integer between ${description start end}";
};
## Create a type that allows an integer value between a given range with a specific ## Create a type that allows an integer value between a given range with a specific
## number of bits. ## number of bits.
@ -317,14 +323,15 @@ lib: {
## @type Int -> Int -> Attrs ## @type Int -> Int -> Attrs
between = between =
start: end: start: end:
assert lib.errors.trace ( assert lib.errors.trace
start <= end (
) "lib.types.numbers.between start must be less than or equal to end"; start <= end
lib.types.withCheck lib.types.number (value: value >= start && value <= end) ) "lib.types.numbers.between start must be less than or equal to end";
// { lib.types.withCheck lib.types.number (value: value >= start && value <= end)
name = "NumberBetween"; // {
description = "numbereger between ${description start end}"; name = "NumberBetween";
}; description = "numbereger between ${description start end}";
};
in in
{ {
inherit between; inherit between;
@ -447,8 +454,7 @@ lib: {
}; };
attrs = { attrs = {
## A type that allows an attribute set containing any type of value. The merged ## A type that allows an attribute set containing any type of value.
## definitions must all be.
## ##
## @type Attrs ## @type Attrs
any = lib.types.create { any = lib.types.create {
@ -480,10 +486,12 @@ lib: {
let let
normalize = normalize =
definition: definition:
builtins.mapAttrs (name: value: { builtins.mapAttrs
__file__ = definition.__file__; (name: value: {
value = value; __file__ = definition.__file__;
}) definition.value; value = value;
})
definition.value;
normalized = builtins.map normalize definitions; normalized = builtins.map normalize definitions;
zipper = zipper =
name: definitions: (lib.options.merge.definitions (location ++ [ name ]) type definitions).optional; name: definitions: (lib.options.merge.definitions (location ++ [ name ]) type definitions).optional;
@ -520,17 +528,19 @@ lib: {
let let
normalize = normalize =
definition: definition:
builtins.mapAttrs (name: value: { builtins.mapAttrs
__file__ = definition.__file__; (name: value: {
value = value; __file__ = definition.__file__;
}) definition.value; value = value;
})
definition.value;
normalized = builtins.map normalize definitions; normalized = builtins.map normalize definitions;
zipper = zipper =
name: definitions: name: definitions:
let let
merged = lib.options.merge.definitions (location ++ [ name ]) type definitions; merged = lib.options.merge.definitions (location ++ [ name ]) type definitions;
in in
merged.optional.value or type.fallback.value or merged.merged; merged.optional.value or type.fallback.value or merged.merged;
in in
builtins.zipAttrsWith zipper normalized; builtins.zipAttrsWith zipper normalized;
getSubOptions = prefix: type.getSubOptions (prefix ++ [ "<name>" ]); getSubOptions = prefix: type.getSubOptions (prefix ++ [ "<name>" ]);
@ -606,25 +616,30 @@ lib: {
merge = merge =
location: definitions: location: definitions:
let let
result = lib.lists.mapWithIndex1 ( result = lib.lists.mapWithIndex1
i: definition: (
lib.lists.mapWithIndex1 ( i: definition:
j: value: lib.lists.mapWithIndex1
let (
resolved = j: value:
lib.options.merge.definitions (location ++ [ "[definition ${builtins.toString i}-entry ${j}]" ]) let
type resolved =
[ lib.options.merge.definitions (location ++ [ "[definition ${builtins.toString i}-entry ${j}]" ])
{ type
file = definition.file; [
value = value; {
} file = definition.file;
]; value = value;
in }
resolved.optional ];
) definition.value in
) definitions; resolved.optional
merged = builtins.concatLists result; )
definition.value
)
definitions;
merged = lib.lists.flatten result;
filtered = builtins.filter (definition: definition ? value) merged; filtered = builtins.filter (definition: definition ? value) merged;
values = lib.options.getDefinitionValues filtered; values = lib.options.getDefinitionValues filtered;
in in
@ -777,11 +792,11 @@ lib: {
## ##
## @type { modules :: List Module, args? :: Attrs, description? :: String | Null, shorthand? :: Bool } -> Attrs ## @type { modules :: List Module, args? :: Attrs, description? :: String | Null, shorthand? :: Bool } -> Attrs
of = of =
settings@{ settings@{ modules
modules, , args ? { }
args ? { }, , description ? null
description ? null, , shorthand ? true
shorthand ? true, ,
}: }:
let let
getModules = builtins.map ( getModules = builtins.map (
@ -790,9 +805,12 @@ lib: {
let let
# TODO: Figure out if we can apply additional attributes to the generated module. # TODO: Figure out if we can apply additional attributes to the generated module.
# Currently this causes issues to do with redefined options. # Currently this causes issues to do with redefined options.
rest = builtins.removeAttrs (lib.attrs.filter ( rest = builtins.removeAttrs
name: value: builtins.elem name lib.modules.VALID_KEYS (lib.attrs.filter
) definition.value) [ "freeform" ]; (
name: value: builtins.elem name lib.modules.VALID_KEYS
)
definition.value) [ "freeform" ];
in in
if definition.value ? config then if definition.value ? config then
rest rest
@ -922,10 +940,12 @@ lib: {
merge = location: definitions: { merge = location: definitions: {
includes = includes =
modules modules
++ builtins.map (definition: { ++ builtins.map
__file__ = "${definition.__file__}; via ${lib.options.getIdentifier location}"; (definition: {
includes = [ definition.value ]; __file__ = "${definition.__file__}; via ${lib.options.getIdentifier location}";
}) definitions; includes = [ definition.value ];
})
definitions;
}; };
getSubOptions = submodule.getSubOptions; getSubOptions = submodule.getSubOptions;
getSubModules = submodule.getSubModules; getSubModules = submodule.getSubModules;
@ -961,10 +981,12 @@ lib: {
location: definitions: location: definitions:
let let
first = builtins.elemAt definitions 0; first = builtins.elemAt definitions 0;
modules = builtins.map (definition: { modules = builtins.map
__file__ = definition.__file__; (definition: {
options = lib.options.create { type = definition.value; }; __file__ = definition.__file__;
}) definitions; options = lib.options.create { type = definition.value; };
})
definitions;
merged = lib.modules.fixup location (lib.options.merge.declarations location modules); merged = lib.modules.fixup location (lib.options.merge.declarations location modules);
in in
if builtins.length definitions == 1 then first.value else merged.type; if builtins.length definitions == 1 then first.value else merged.type;
@ -1197,10 +1219,12 @@ lib: {
merge = merge =
location: definitions: location: definitions:
submodule.merge location ( submodule.merge location (
builtins.map (definition: { builtins.map
__file__ = definition.__file__; (definition: {
value = normalize definition; __file__ = definition.__file__;
}) definitions value = normalize definition;
})
definitions
); );
}; };
}; };

View file

@ -8,10 +8,10 @@
}, },
"locked": { "locked": {
"dir": "foundation", "dir": "foundation",
"dirtyRev": "42e69f7d43c0fb108ac70fde118c4a8ff6a777b0-dirty", "dirtyRev": "3f9d287065ac685ce500c2cddb35428b2927f5a2-dirty",
"dirtyShortRev": "42e69f7-dirty", "dirtyShortRev": "3f9d287-dirty",
"lastModified": 1720466389, "lastModified": 1720514984,
"narHash": "sha256-Zrmbcb+42r6eZV05QbcG2znHXrOh0sbdRaEZYxV0C7A=", "narHash": "sha256-AuixwSlYk34Z6+GEc7y4QotF3Hk963zC9I9hAwX5KCE=",
"type": "git", "type": "git",
"url": "file:../?dir=foundation" "url": "file:../?dir=foundation"
}, },
@ -24,10 +24,10 @@
"lib": { "lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"dirtyRev": "42e69f7d43c0fb108ac70fde118c4a8ff6a777b0-dirty", "dirtyRev": "3f9d287065ac685ce500c2cddb35428b2927f5a2-dirty",
"dirtyShortRev": "42e69f7-dirty", "dirtyShortRev": "3f9d287-dirty",
"lastModified": 1720466389, "lastModified": 1720514984,
"narHash": "sha256-Zrmbcb+42r6eZV05QbcG2znHXrOh0sbdRaEZYxV0C7A=", "narHash": "sha256-AuixwSlYk34Z6+GEc7y4QotF3Hk963zC9I9hAwX5KCE=",
"type": "git", "type": "git",
"url": "file:../?dir=lib" "url": "file:../?dir=lib"
}, },

View file

@ -20,8 +20,6 @@ in
hooks = lib.packages.hooks.create dependencies context; hooks = lib.packages.hooks.create dependencies context;
cflags = builtins.concatStringsSep " " (context.build.host.cflags or [ ]);
phasesWithHooks = phasesWithHooks =
let let
all = lib.lists.flatten [ all = lib.lists.flatten [

View file

@ -101,6 +101,7 @@ in
dependencies = lib.attrs.selectOrThrow path collected; dependencies = lib.attrs.selectOrThrow path collected;
contexts = builtins.map (dependency: dependency.context or { }) dependencies; contexts = builtins.map (dependency: dependency.context or { }) dependencies;
result = lib.modules.run { result = lib.modules.run {
prefix = [ "<package>" ];
modules = builtins.map (context: { config = context; }) contexts ++ [ modules = builtins.map (context: { config = context; }) contexts ++ [
{ {
freeform = lib.types.any; freeform = lib.types.any;
@ -210,13 +211,15 @@ in
path: path:
let let
dependencies = lib.attrs.selectOrThrow path collected; dependencies = lib.attrs.selectOrThrow path collected;
hooks = builtins.map ( hooks = builtins.map
dependency: (
let dependency:
getHooks = dependency.hooks or (lib.fp.const { }); let
in getHooks = dependency.hooks or (lib.fp.const { });
getHooks ctx in
) dependencies; getHooks ctx
)
dependencies;
in in
hooks; hooks;
in in

View file

@ -214,7 +214,7 @@ in
}; };
submodule = submodule =
{ config }: { config, meta }:
let let
build = config.platform.build; build = config.platform.build;
host = config.platform.host; host = config.platform.host;
@ -230,6 +230,34 @@ in
default.value = [ ]; default.value = [ ];
}; };
extend = lib.options.create {
description = "Extend the package definition.";
type = lib.types.function lib.types.raw;
internal = true;
writable = false;
default.value = module:
let
normalized =
if builtins.isList module then
module
else if builtins.isFunction module || module ? config then
[ module ]
else
[{
config = module;
}];
result = meta.extend {
modules =
normalized ++ [
{
config.__modules__ = lib.modules.overrides.force (config.__modules__ ++ normalized);
}
];
};
in
result.config;
};
meta = { meta = {
description = lib.options.create { description = lib.options.create {
description = "The description for the package."; description = "The description for the package.";

View file

@ -26,8 +26,18 @@ in
builder = builders.basic; builder = builders.basic;
deps = {
build = {
host = {
c = packages.aux.c.versions.latest.extend {
propagate = true;
};
};
};
};
context = { context = {
cflags = [ "-I $AUX_B/include" ]; "foundation:cflags" = [ "-I $AUX_B/include" ];
}; };
hooks = ctx: { "aux:b:env" = lib.dag.entry.after [ "unpack" ] ''export AUX_B=${config.package}''; }; hooks = ctx: { "aux:b:env" = lib.dag.entry.after [ "unpack" ] ''export AUX_B=${config.package}''; };

View file

@ -0,0 +1,44 @@
{ config }:
let
inherit (config) lib builders packages;
in
{
config.packages.aux.c = {
versions = {
"latest" =
{ config }:
{
options = {
custom = lib.options.create { type = lib.types.bool; };
};
config = {
meta = {
platforms = [ "i686-linux" ];
};
name = "${config.pname}-${config.version}";
custom = true;
pname = "c";
version = "1.0.0";
builder = builders.basic;
context = {
"foundation:cflags" = [ "-I $AUX_C/include" ];
};
hooks = ctx: { "aux:c:env" = lib.dag.entry.after [ "unpack" ] ''export AUX_C=${config.package}''; };
phases = {
install = ''
echo "c" > $out
'';
};
};
};
};
};
}

View file

@ -11,6 +11,7 @@ in
./foundation ./foundation
./aux/a.nix ./aux/a.nix
./aux/b.nix ./aux/b.nix
./aux/c.nix
]; ];
options = { options = {
@ -55,32 +56,34 @@ in
config = { config = {
packages.cross = lib.attrs.generate doubles ( packages.cross = lib.attrs.generate doubles (
system: system:
builtins.mapAttrs ( builtins.mapAttrs
namespace: (
builtins.mapAttrs ( namespace:
name: alias: builtins.mapAttrs (
let name: alias:
setHost = let
package: setHost =
package package:
// { package
__modules__ = package.__modules__ ++ [ // {
{ __modules__ = package.__modules__ ++ [
config.platform = { {
host = lib.modules.override 5 system; config.platform = {
target = lib.modules.override 5 system; host = lib.modules.override 5 system;
}; target = lib.modules.override 5 system;
} };
]; }
}; ];
};
updated = alias // { updated = alias // {
versions = builtins.mapAttrs (version: package: setHost package) alias.versions; versions = builtins.mapAttrs (version: package: setHost package) alias.versions;
}; };
in in
updated updated
)
) )
) packages packages
); );
}; };
} }

View file

@ -11,6 +11,13 @@ let
; ;
in in
{ {
config.packages.context.options = {
"foundation:cflags" = lib.options.create {
type = lib.types.list.of lib.types.string;
default.value = [ ];
};
};
config.packages.foundation.gcc = { config.packages.foundation.gcc = {
versions = { versions = {
"latest" = "latest" =