commit a994a0cbacbe9d71e21abf78898d2853a05505e4 Author: Gabriel Arazas Date: Mon Jul 1 15:14:48 2024 +0800 wrapper-manager-fds: init It's a prototype for now, yeah. It'll be improved. diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..917af49 --- /dev/null +++ b/default.nix @@ -0,0 +1,18 @@ +# We just keep this attribute set for forward compatability in case it became +# required for users to pass something like the nixpkgs instance. +{ }: + +{ + nixosModules = rec { + default = wrapper-manager; + wrapper-manager = ./modules/env/nixos; + }; + + homeModules = rec { + default = wrapper-manager; + wrapper-manager = ./modules/env/home-manager; + }; + + wrapperManagerModules.default = ./modules/wrapper-manager; + wrapperManagerLib = ./lib; +} diff --git a/lib/build-support.nix b/lib/build-support.nix new file mode 100644 index 0000000..bfc0d24 --- /dev/null +++ b/lib/build-support.nix @@ -0,0 +1,60 @@ +# Unless you're a third-party module author wanting to integrate +# wrapper-manager to whatever bespoke configuration environment, there is +# almost no reason to use the following functions, really. +{ pkgs, lib, self }: + +{ + /* The build function for making simple and single executable + wrappers similar to nixpkgs builders for various ecosystems (for example, + `buildGoPackage` and `buildRustPackage`). + */ + mkWrapper = { + arg0, + executableName ? arg0, + makeWrapperArgs ? [ ], + + nativeBuildInputs ? [ ], + passthru ? { }, + }@args: + pkgs.runCommand "wrapper-manager-script-${arg0}" ( + (builtins.removeAttrs args [ "executableName" "arg0" ]) + // { + inherit makeWrapperArgs; + nativeBuildInputs = nativeBuildInputs ++ [ pkgs.makeWrapper ]; + + passthru = passthru // { + wrapperScript = { inherit arg0 executableName; }; + }; + } + ) '' + mkdir -p $out/bin + makeWrapper ${arg0} "$out/bin/${executableName}" ''${makeWrapperArgs[@]} + ''; + + mkWrappedPackage = { + package, + executableName ? package.meta.mainProgram or package.pname, + + postBuild ? "", + nativeBuildInputs ? [ ], + makeWrapperArgs ? [ ], + passthru ? { }, + }@args: + pkgs.symlinkJoin ( + (builtins.removeAttrs args [ "package" "executableName" ]) + // { + name = "wrapper-manager-wrapped-package-${package.pname}"; + paths = [ package ]; + + inherit makeWrapperArgs; + nativeBuildInputs = nativeBuildInputs ++ [ pkgs.makeWrapper ]; + passthru = passthru // { + wrapperScript = { inherit executableName package; }; + }; + postBuild = '' + ${postBuild} + wrapProgram "${lib.getExe' package executableName}" ''${makeWrapperArgs[@]} + ''; + }); + +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..63f37e1 --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,26 @@ +# The wrapper-manager library set. It should only require a nixpkgs instance to +# make initializing this set easier. This what makes it possible to be used as +# part of the module environments and as a standalone library. +# +# Since this library set is typically modularly set in nixpkgs module +# environments, we'll have to make sure it doesn't contain any functions that +# should return a nixpkgs module or anything of the sort. Basically, this +# should remain as a utility function that is usable outside of the nixpkgs +# module. +{ pkgs }: + +pkgs.lib.makeExtensible + (self: + let + callLibs = file: import file { inherit (pkgs) lib; inherit pkgs self; }; + in + { + build-support = callLibs ./build-support.nix; + env = callLibs ./env.nix; + utils = callLibs ./utils.nix; + + inherit (self.build-support) mkWrapper mkWrappedPackage; + inherit (self.env) build eval; + inherit (self.utils) getBin getLibexec; + }) + diff --git a/lib/env.nix b/lib/env.nix new file mode 100644 index 0000000..7d98d17 --- /dev/null +++ b/lib/env.nix @@ -0,0 +1,22 @@ +{ pkgs, lib, self }: + +rec { + /* Given the attrset for evaluating a wrapper-manager module, return a + derivation containing the wrapper. + */ + build = args: + (eval args).config.build.toplevel; + + /* Evaluate a wrapper-manager configuration. */ + eval = { + pkgs, + modules ? [ ], + specialArgs ? { }, + }: + pkgs.lib.evalModules { + modules = [ ../modules/wrapper-manager ] ++ modules; + specialArgs = specialArgs // { + modulesPath = builtins.toString ../modules/wrapper-manager; + }; + }; +} diff --git a/lib/utils.nix b/lib/utils.nix new file mode 100644 index 0000000..b65f2c8 --- /dev/null +++ b/lib/utils.nix @@ -0,0 +1,15 @@ +{ pkgs, lib, self }: + +rec { + /* + Given a list of derivations, return a list of the store path with the `bin` + output (or at least with "/bin" in each of the paths). + */ + getBin = drvs: + builtins.map (v: lib.getBin v) drvs; + + /* + */ + getLibexec = drvs: + builtins.map (v: "${v}/libexec") drvs; +} diff --git a/modules/wrapper-manager/README.adoc b/modules/wrapper-manager/README.adoc new file mode 100644 index 0000000..34ee3e4 --- /dev/null +++ b/modules/wrapper-manager/README.adoc @@ -0,0 +1,5 @@ += wrapper-manager modules +:toc: + +This is the module set of the wrapper-manager module environment. +Just take note that we're following the runtime shell of nixpkgs which is GNU Bash as of 2024-06-30. diff --git a/modules/wrapper-manager/base.nix b/modules/wrapper-manager/base.nix new file mode 100644 index 0000000..970de4b --- /dev/null +++ b/modules/wrapper-manager/base.nix @@ -0,0 +1,112 @@ +{ config, lib, ... }: + +let + flagType = with lib.types; listOf (coercedTo anything (x: builtins.toString x) str); +in +{ + options = { + prependArgs = lib.mkOption { + type = flagType; + description = '' + A list of arguments to be prepended to the user-given argument for the + wrapper script. + ''; + default = [ ]; + example = lib.literalExpression '' + [ + "--config" ./config.conf + ] + ''; + }; + + appendArgs = lib.mkOption { + type = flagType; + description = '' + A list of arguments to be appended to the user-given argument for the + wrapper script. + ''; + default = [ ]; + example = lib.literalExpression '' + [ + "--name" "doggo" + "--location" "Your mom's home" + ] + ''; + }; + + arg0 = lib.mkOption { + type = lib.types.str; + description = '' + The first argument of the wrapper script. This option is used when the + {option}`build.variant` is `executable`. + ''; + example = lib.literalExpression "lib.getExe' pkgs.neofetch \"neofetch\""; + }; + + package = lib.mkOption { + type = lib.types.package; + description = '' + The package to be wrapped. This is used only when the + {option}`build.variant` is set to `package.` + ''; + example = lib.literalExpression "pkgs.hello"; + }; + + env = lib.mkOption { + type = with lib.types; attrsOf str; + description = '' + A set of environment variables to be declared in the wrapper script. + ''; + default = { }; + example = { + "FOO_TYPE" = "custom"; + "FOO_LOG_STYLE" = "systemd"; + }; + }; + + executableName = lib.mkOption { + type = lib.types.nonEmptyStr; + description = '' + The name of the executable of the wrapper script. + + This option behaves differently depending on {option}`build.variant`. + + - When the build variant option is `executable`, it sets the name of the + wrapper script. + - When the build variant option is `package`, it depends on the name on + one of the executables from the given package. + ''; + default = + if config.build.variant == "executable" then + lib.tail (lib.path.subpath.components config.arg0) + else + config.package.meta.mainProgram or config.package.pname; + example = "custom-name"; + }; + + pathAdd = lib.mkOption { + type = with lib.types; listOf path; + description = '' + A list of paths to be added as part of the `PATH` environment variable. + ''; + default = [ ]; + example = lib.literalExpression '' + wrapperManagerLib.getBin (with pkgs; [ + yt-dlp + gallery-dl + ]) + ''; + }; + + preScript = lib.mkOption { + type = lib.types.lines; + description = '' + Script to run before the main executable. + ''; + default = ""; + example = lib.literalExpression '' + echo "HELLO WORLD!" + ''; + }; + }; +} diff --git a/modules/wrapper-manager/build.nix b/modules/wrapper-manager/build.nix new file mode 100644 index 0000000..b74ebea --- /dev/null +++ b/modules/wrapper-manager/build.nix @@ -0,0 +1,76 @@ +{ config, lib, pkgs, wrapperManagerLib, ... }: + +{ + options.build = { + variant = lib.mkOption { + type = lib.types.enum [ "executable" "package" ]; + description = '' + Tells how should wrapper-manager wrap the executable. The toplevel + derivation resulting from the module environment will vary depending on + the value. + + - With `executable`, the wrapper is a lone executable wrapper script in + `$OUT/bin` subdirectory in the output. + + - With `package`, wrapper-manager creates a wrapped package with all of + the output contents intact. + ''; + default = "executable"; + example = "package"; + }; + + extraWrapperArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + A list of extra arguments to be passed to the `makeWrapper` nixpkgs + setup hook function. + ''; + example = [ "--inherit-argv0" ]; + }; + + extraArgs = lib.mkOption { + type = with lib.types; attrsOf anything; + description = '' + A attrset of extra arguments to be passed to the + `wrapperManagerLib.mkWrapper` function. This will also be passed as + part of the derivation attribute into the resulting script from + {option}`preScript`. + ''; + }; + + toplevel = lib.mkOption { + type = lib.types.package; + readOnly = true; + internal = true; + description = "A derivation containing the wrapper script."; + }; + }; + + config.build = { + extraWrapperArgs = [ + "--argv0" (config.executableName or config.arg0) + "--add-flags" config.prependFlags + "--append-flags" config.appendFlags + ] + ++ (lib.mapAttrsToList (n: v: "--set ${lib.escapeShellArg n} ${lib.escapeShellArg v}") config.env) + ++ (builtins.map (v: "--prefix 'PATH' ':' ${lib.escapeShellArg v}") config.pathAdd) + ++ (lib.optionals (config.preScript != "") ( + let + preScript = + pkgs.runCommand "wrapper-script-prescript-${config.executableName}" config.build.extraArgs config.preScript; + in + "--run" preScript)); + + toplevel = + if config.build.variant == "executable" then + wrapperManagerLib.mkWrapper (config.build.extraArgs // { + inherit (config) arg0 executableName; + makeWrapperArgs = config.build.extraWrapperArgs; + }) + else + wrapperManagerLib.mkWrappedPackage (config.build.extraArgs // { + inherit (config) package executableName; + makeWrapperArgs = config.build.extraWrapperArgs; + }); + }; +} diff --git a/modules/wrapper-manager/default.nix b/modules/wrapper-manager/default.nix new file mode 100644 index 0000000..f43ff1b --- /dev/null +++ b/modules/wrapper-manager/default.nix @@ -0,0 +1,7 @@ +{ + imports = [ + ./base.nix + ./build.nix + ./extra-args.nix + ]; +} diff --git a/modules/wrapper-manager/extra-args.nix b/modules/wrapper-manager/extra-args.nix new file mode 100644 index 0000000..0982f3b --- /dev/null +++ b/modules/wrapper-manager/extra-args.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: + +{ + _module.args = { + wrapperManagerLib = import ../../lib { inherit pkgs; }; + }; +} diff --git a/npins/default.nix b/npins/default.nix new file mode 100644 index 0000000..5e7d086 --- /dev/null +++ b/npins/default.nix @@ -0,0 +1,80 @@ +# Generated by npins. Do not modify; will be overwritten regularly +let + data = builtins.fromJSON (builtins.readFile ./sources.json); + version = data.version; + + mkSource = + spec: + assert spec ? type; + let + path = + if spec.type == "Git" then + mkGitSource spec + else if spec.type == "GitRelease" then + mkGitSource spec + else if spec.type == "PyPi" then + mkPyPiSource spec + else if spec.type == "Channel" then + mkChannelSource spec + else + builtins.throw "Unknown source type ${spec.type}"; + in + spec // { outPath = path; }; + + mkGitSource = + { + repository, + revision, + url ? null, + hash, + branch ? null, + ... + }: + assert repository ? type; + # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository + # In the latter case, there we will always be an url to the tarball + if url != null then + (builtins.fetchTarball { + inherit url; + sha256 = hash; # FIXME: check nix version & use SRI hashes + }) + else + assert repository.type == "Git"; + let + urlToName = + url: rev: + let + matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url; + + short = builtins.substring 0 7 rev; + + appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; + in + "${if matched == null then "source" else builtins.head matched}${appendShort}"; + name = urlToName repository.url revision; + in + builtins.fetchGit { + url = repository.url; + rev = revision; + inherit name; + # hash = hash; + }; + + mkPyPiSource = + { url, hash, ... }: + builtins.fetchurl { + inherit url; + sha256 = hash; + }; + + mkChannelSource = + { url, hash, ... }: + builtins.fetchTarball { + inherit url; + sha256 = hash; + }; +in +if version == 3 then + builtins.mapAttrs (_: mkSource) data.pins +else + throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" diff --git a/npins/sources.json b/npins/sources.json new file mode 100644 index 0000000..5c6d623 --- /dev/null +++ b/npins/sources.json @@ -0,0 +1,53 @@ +{ + "pins": { + "home-manager-stable": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "home-manager" + }, + "branch": "release-24.05", + "revision": "a1fddf0967c33754271761d91a3d921772b30d0e", + "url": "https://github.com/nix-community/home-manager/archive/a1fddf0967c33754271761d91a3d921772b30d0e.tar.gz", + "hash": "1vvrrk14vrhb6drj3fy8snly0sf24x3402ykb9q5j1gy99vvqqq6" + }, + "home-manager-unstable": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "home-manager" + }, + "branch": "master", + "revision": "36317d4d38887f7629876b0e43c8d9593c5cc48d", + "url": "https://github.com/nix-community/home-manager/archive/36317d4d38887f7629876b0e43c8d9593c5cc48d.tar.gz", + "hash": "1wrz1s78fhd8fvqsxkn10rzig7w8pxfbf1xff2rlxl7zr1k5dvx8" + }, + "nixos-stable": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixpkgs" + }, + "branch": "nixos-24.05", + "revision": "89c49874fb15f4124bf71ca5f42a04f2ee5825fd", + "url": "https://github.com/NixOS/nixpkgs/archive/89c49874fb15f4124bf71ca5f42a04f2ee5825fd.tar.gz", + "hash": "07mr5xmdba3i5qw68kvxs0w1l70pv6pg636dqqxi6s91hiazv4n8" + }, + "nixos-unstable": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixpkgs" + }, + "branch": "nixos-unstable", + "revision": "b2852eb9365c6de48ffb0dc2c9562591f652242a", + "url": "https://github.com/NixOS/nixpkgs/archive/b2852eb9365c6de48ffb0dc2c9562591f652242a.tar.gz", + "hash": "0zrl64ndfkkc4zhykrnc03b9ymp793zzmjqy3jfi9ckkni5vviqb" + } + }, + "version": 3 +} \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..b95a76a --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import { } }: + +pkgs.mkShell { + inputsFrom = with pkgs; [ nix ]; + packages = with pkgs; [ + npins + treefmt + nixpkgs-fmt + ]; +}