Skip to content

Commit a09dbdc

Browse files
authored
[package outputs] generate buildInputs for package-outputs in flake (#1721)
## Summary This PR reads the `outputs` field from the Config for a package. For packages with non-default outputs in the config, we specify: 1. They are not to be taken from the binary cache 2. They are exempt from the `flakeInput.buildInputs()` that are specified in the generated flake's devshell.buildInputs Instead, we generate a [`symlinkJoin` derivation](https://nixos.org/manual/nixpkgs/stable/#trivial-builder-symlinkJoin) of the form: ``` (pkgs.symlinkJoin { name = "prometheus-combined"; paths = [ # each output is printed here nixpkgs-fd04be-pkgs.prometheus.cli nixpkgs-fd04be-pkgs.prometheus.out ]; }) ``` This `prometheus-combined` derivation is useful so that when we derive the nix-profile from the flake's buildInputs as in #1692, it is treated as a single package. Without this, we could inline each output as a distinct buildInput in the flake's devshell, but then the nix profile would treat each output as its own package. ## How was it tested? Added testscript unit-test. Also, ran the same steps inside the testscript manually. setup: ``` # add package with multiple outputs devbox add prometheus --outputs cli,out # add package for a specific nixpkgs commit hash (as a control) devbox add github:nixos/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff#hello ``` then the generated flake is: ``` ❯ cat .devbox/gen/flake/flake.nix { description = "A devbox shell"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff"; nixpkgs-fd04be.url = "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff"; gh-nixos-nixpkgs-fd04bea4cbf76f86f244b9e2549fca066db8ddff.url = "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff"; }; outputs = { self, nixpkgs, nixpkgs-fd04be, gh-nixos-nixpkgs-fd04bea4cbf76f86f244b9e2549fca066db8ddff, }: let pkgs = nixpkgs.legacyPackages.x86_64-darwin; nixpkgs-fd04be-pkgs = (import nixpkgs-fd04be { system = "x86_64-darwin"; config.allowUnfree = true; config.permittedInsecurePackages = [ ]; }); gh-nixos-nixpkgs-fd04bea4cbf76f86f244b9e2549fca066db8ddff-pkgs = (import gh-nixos-nixpkgs-fd04bea4cbf76f86f244b9e2549fca066db8ddff { system = "x86_64-darwin"; config.allowUnfree = true; config.permittedInsecurePackages = [ ]; }); in { devShells.x86_64-darwin.default = pkgs.mkShell { buildInputs = [ (builtins.fetchClosure { fromStore = "https://cache.nixos.org"; fromPath = "/nix/store/0djljz0g4s6f55xcnw7fpzcy7af7rxid-go-1.21.4"; inputAddressed = true; }) (pkgs.symlinkJoin { name = "prometheus-combined"; paths = [ nixpkgs-fd04be-pkgs.prometheus.cli nixpkgs-fd04be-pkgs.prometheus.out ]; }) gh-nixos-nixpkgs-fd04bea4cbf76f86f244b9e2549fca066db8ddff-pkgs.hello ]; }; }; } ``` this leads to `$buildInputs` in a `devbox shell` to be: ``` ❯ echo $buildInputs /nix/store/0djljz0g4s6f55xcnw7fpzcy7af7rxid-go-1.21.4 /nix/store/9dn3rzv04x63n0kz2jwpgz82rdlsa56h-prometheus-combined /nix/store/4xy6iv0ph2v6w7n06cw5ra7cmyvignkm-hello-2.12.1 ``` where the `prometheus-combined` derivation has the `cli` and `out` outputs combined: ``` ❯ ls -al /nix/store/9dn3rzv04x63n0kz2jwpgz82rdlsa56h-prometheus-combined/bin/ total 0 dr-xr-xr-x 4 root wheel 128 Dec 31 1969 . dr-xr-xr-x 4 root wheel 128 Dec 31 1969 .. lrwxr-xr-x 1 root wheel 76 Dec 31 1969 prometheus -> /nix/store/hizpsf2f5gc7l810328382xicjb9gc73-prometheus-2.48.1/bin/prometheus lrwxr-xr-x 1 root wheel 78 Dec 31 1969 promtool -> /nix/store/8x6psdqn3945pbs0ww5wanmfhvvz2iyl-prometheus-2.48.1-cli/bin/promtool ```
1 parent 03a1c2b commit a09dbdc

File tree

6 files changed

+101
-3
lines changed

6 files changed

+101
-3
lines changed

internal/devpkg/narinfo_cache.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ func (p *Package) IsInBinaryCache() (bool, error) {
2727
if p.PatchGlibc {
2828
return false, nil
2929
}
30+
// Packages with non-default outputs are not to be taken from the binary cache.
31+
if len(p.Outputs) > 0 {
32+
return false, nil
33+
}
3034
if eligible, err := p.isEligibleForBinaryCache(); err != nil {
3135
return false, err
3236
} else if !eligible {

internal/devpkg/package.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type Package struct {
7878
// example: github:nixos/nixpkgs/5233fd2ba76a3accb5aaa999c00509a11fd0793c#hello
7979
Raw string
8080

81+
// Outputs is a list of outputs to build from the package's derivation.
82+
// If empty, the default output is used.
83+
Outputs []string
84+
8185
// PatchGlibc applies a function to the package's derivation that
8286
// patches any ELF binaries to use the latest version of nixpkgs#glibc.
8387
PatchGlibc bool
@@ -113,6 +117,7 @@ func PackagesFromConfig(config *devconfig.Config, l lock.Locker) []*Package {
113117
pkg := newPackage(cfgPkg.VersionedName(), cfgPkg.IsEnabledOnPlatform(), l)
114118
pkg.DisablePlugin = cfgPkg.DisablePlugin
115119
pkg.PatchGlibc = cfgPkg.PatchGlibc && nix.SystemIsLinux()
120+
pkg.Outputs = cfgPkg.Outputs
116121
result = append(result, pkg)
117122
}
118123
return result
@@ -126,6 +131,7 @@ func PackageFromStringWithOptions(raw string, locker lock.Locker, opts devopt.Ad
126131
pkg := PackageFromStringWithDefaults(raw, locker)
127132
pkg.DisablePlugin = opts.DisablePlugin
128133
pkg.PatchGlibc = opts.PatchGlibc
134+
pkg.Outputs = opts.Outputs
129135
return pkg
130136
}
131137

internal/shellgen/flake_input.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package shellgen
22

33
import (
44
"context"
5+
"errors"
56
"runtime/trace"
67
"slices"
78
"strings"
@@ -50,9 +51,54 @@ func (f *flakeInput) PkgImportName() string {
5051
return f.Name + "-pkgs"
5152
}
5253

54+
type SymlinkJoin struct {
55+
Name string
56+
Paths []string
57+
}
58+
59+
// BuildInputsForSymlinkJoin returns a list of SymlinkJoin objects that can be used
60+
// as the buildInput. Used for packages that have non-default outputs that need to
61+
// be combined into a single buildInput.
62+
func (f *flakeInput) BuildInputsForSymlinkJoin() ([]*SymlinkJoin, error) {
63+
joins := []*SymlinkJoin{}
64+
for _, pkg := range f.Packages {
65+
// skip packages that have no non-default outputs
66+
if len(pkg.Outputs) == 0 {
67+
continue
68+
}
69+
70+
attributePath, err := pkg.FullPackageAttributePath()
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
if pkg.PatchGlibc {
76+
return nil, errors.New("patch_glibc is not yet supported for packages with non-default outputs")
77+
}
78+
joins = append(joins, &SymlinkJoin{
79+
Name: pkg.String() + "-combined",
80+
Paths: lo.Map(pkg.Outputs, func(output string, _ int) string {
81+
if !f.IsNixpkgs() {
82+
return f.Name + "." + attributePath + "." + output
83+
}
84+
parts := strings.Split(attributePath, ".")
85+
return f.PkgImportName() + "." + strings.Join(parts[2:], ".") + "." + output
86+
}),
87+
})
88+
}
89+
return joins, nil
90+
}
91+
5392
func (f *flakeInput) BuildInputs() ([]string, error) {
5493
var err error
55-
attributePaths := lo.Map(f.Packages, func(pkg *devpkg.Package, _ int) string {
94+
95+
// Filter out packages that have non-default outputs
96+
// These are handled in BuildInputsForSymlinkJoin
97+
packages := lo.Filter(f.Packages, func(pkg *devpkg.Package, _ int) bool {
98+
return len(pkg.Outputs) == 0
99+
})
100+
101+
attributePaths := lo.Map(packages, func(pkg *devpkg.Package, _ int) string {
56102
attributePath, attributePathErr := pkg.FullPackageAttributePath()
57103
if attributePathErr != nil {
58104
err = attributePathErr

internal/shellgen/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var tmplFS embed.FS
3030
// devbox.PrintEnv, which is the core function from which devbox shell/run/direnv
3131
// functionality is derived.
3232
func GenerateForPrintEnv(ctx context.Context, devbox devboxer) error {
33-
defer trace.StartRegion(ctx, "generateShellFiles").End()
33+
defer trace.StartRegion(ctx, "GenerateForPrintEnv").End()
3434

3535
plan, err := newFlakePlan(ctx, devbox)
3636
if err != nil {

internal/shellgen/tmpl/flake_remove_nixpkgs.nix.tmpl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,17 @@
4646
})
4747
{{- end }}
4848
{{- end }}
49-
{{- range .FlakeInputs }}
49+
{{- range $_, $flakeInput := .FlakeInputs }}
50+
{{- range .BuildInputsForSymlinkJoin }}
51+
(pkgs.symlinkJoin {
52+
name = "{{.Name}}";
53+
paths = [
54+
{{- range .Paths }}
55+
{{.}}
56+
{{- end }}
57+
];
58+
})
59+
{{- end }}
5060
{{- range .BuildInputs }}
5161
{{.}}
5262
{{- end }}

testscripts/add/add_outputs.test.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Testscript to add packages with non-default outputs
2+
3+
exec devbox init
4+
5+
# Add prometheus with default outputs. It will not have promtool.
6+
exec devbox add prometheus
7+
exec devbox run -- prometheus --version
8+
! exec devbox run -- promtool --version
9+
10+
# Add prometheus with cli and out outputs. It will have promtool as well.
11+
exec devbox add prometheus --outputs cli,out
12+
json.superset devbox.json expected_devbox.json
13+
exec devbox run -- promtool --version
14+
exec devbox run -- prometheus --version
15+
16+
17+
18+
-- devbox.json --
19+
{
20+
"packages": [
21+
]
22+
}
23+
24+
-- expected_devbox.json --
25+
{
26+
"packages": {
27+
"prometheus": {
28+
"version": "latest",
29+
"outputs": ["cli", "out"]
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)