Skip to content

Commit 197053c

Browse files
authored
[flakes] Assign higher priority to later packages to resolve conflicting packages error (#738)
## Summary We want to re-try nix profile install with package conflicts by setting a higher priority (lower priority number) for later packages. ## How was it tested? using the github.com/jetpack-io/devbox-examples repo before: ``` devbox-examples/development/ruby on  main [!?] on ☁️ (us-east-2) on ☁️ [email protected] 💫 initial project sync ❯ devbox shell Ensuring packages are installed. Installing 2 packages: bundler, ruby_3_1. [1/2] bundler [1/2] bundler: Success [2/2] ruby_3_1 error: files '/nix/store/spgr12gk13af8flz7akbs18fj4whqss2-bundler-2.4.5/bin/bundle' and '/nix/store/l4wmx8lfn6hlcfmbyhmksm024f8hixm1-ruby-3.1.2/bin/bundle' have the same priority 5; use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' or type 'nix profile install --help' if using 'nix profile' to find out howto change the priority of one of the conflicting packages (0 being the highest priority) [2/2] ruby_3_1: Fail Error: Command: /nix/var/nix/profiles/default/bin/nix profile install --profile /Users/savil/code/jetpack/devbox-examples/development/ruby/.devbox/nix/profile/default --impure nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#ruby_3_1 --extra-experimental-features ca-derivations --option experimental-features nix-command flakes --priority 5: exit status 1 Error: There was an internal error. Run with DEVBOX_DEBUG=1 for a detailed error message, and consider reporting it at https://github.com/jetpack-io/devbox/issues ``` Then I added `devbox global add zig` to test the sorting for global packages first and then project packages later. after: ``` ❯ DEVBOX_DEBUG=0 devbox shell Ensuring packages are installed. Installing 3 packages: zig, bundler, ruby_3_1. [1/3] zig [1/3] zig: Success [2/3] bundler [2/3] bundler: Success [3/3] ruby_3_1 [3/3] ruby_3_1: Success Starting a devbox shell... (devbox) ``` after with DEVBOX_DEBUG=1: ``` ❯ DEVBOX_DEBUG=1 devbox shell ... Installing 3 packages: zig, bundler, ruby_3_1. [DEBUG] 2023/03/09 21:19:24 /Users/savil/code/jetpack/devbox/internal/impl/packages.go:246: ERROR: resetProfileDirForFlakes error: lstat /Users/savil/code/jetpack/devbox-examples/development/ruby/.devbox/nix/profile/default: no such file or directory [1/3] zig [1/3] zig: Success [2/3] bundler [2/3] bundler: Success [3/3] ruby_3_1 [DEBUG] 2023/03/09 21:19:27 /Users/savil/code/jetpack/devbox/internal/nix/writer.go:39: Hiding output for user: error: files '/nix/store/spgr12gk13af8flz7akbs18fj4whqss2-bundler-2.4.5/bin/bundle' and '/nix/store/l4wmx8lfn6hlcfmbyhmksm024f8hixm1-ruby-3.1.2/bin/bundle' have the same priority 5; use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' or type 'nix profile install --help' if using 'nix profile' to find out how to change the priority of one of the conflicting packages (0 being the highest priority) [DEBUG] 2023/03/09 21:19:27 /Users/savil/code/jetpack/devbox/internal/nix/profile.go:286: Re-trying nix profile install with priority 4 for package ruby_3_1 [3/3] ruby_3_1: Success Starting a devbox shell... ```
1 parent ce44040 commit 197053c

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

internal/impl/packages.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8+
"sort"
89
"strings"
910

1011
"github.com/fatih/color"
@@ -272,6 +273,17 @@ func (d *Devbox) addPackagesToProfile(mode installMode) error {
272273
return nil
273274
}
274275

276+
// Packages with higher priority number i.e. lower actual priority
277+
// are to be installed first, so that any conflicts are resolved in favor
278+
// of later packages (having lower priority number i.e. higher actual priority)
279+
//
280+
// We use stable sort so that users can manually change the order of packages
281+
// in their configs, if they have particular opinions about which package should
282+
// win any conflicts.
283+
sort.SliceStable(pkgs, func(i, j int) bool {
284+
return d.getPackagePriority(pkgs[i]) > d.getPackagePriority(pkgs[j])
285+
})
286+
275287
var msg string
276288
if len(pkgs) == 1 {
277289
msg = fmt.Sprintf("Installing package: %s.", pkgs[0])
@@ -293,7 +305,7 @@ func (d *Devbox) addPackagesToProfile(mode installMode) error {
293305

294306
if err := nix.ProfileInstall(&nix.ProfileInstallArgs{
295307
CustomStepMessage: stepMsg,
296-
ExtraFlags: []string{"--priority", d.getPackagePriority(pkg)},
308+
Priority: d.getPackagePriority(pkg),
297309
NixpkgsCommit: d.cfg.Nixpkgs.Commit,
298310
Package: pkg,
299311
ProfilePath: profileDir,

internal/nix/profile.go

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/fatih/color"
1313
"github.com/pkg/errors"
1414
"go.jetpack.io/devbox/internal/boxcli/featureflag"
15+
"go.jetpack.io/devbox/internal/debug"
1516
)
1617

1718
// ProfileListItems returns a list of the installed packages
@@ -186,6 +187,7 @@ func (item *NixProfileListItem) String() string {
186187

187188
type ProfileInstallArgs struct {
188189
CustomStepMessage string
190+
Priority string
189191
ExtraFlags []string
190192
NixpkgsCommit string
191193
Package string
@@ -206,32 +208,91 @@ func ProfileInstall(args *ProfileInstallArgs) error {
206208
fmt.Fprintf(args.Writer, "%s\n", stepMsg)
207209
}
208210

209-
cmd := exec.Command("nix", "profile", "install",
211+
err := profileInstall(args)
212+
if err != nil {
213+
if errors.Is(err, ErrPackageNotFound) {
214+
return err
215+
}
216+
217+
fmt.Fprintf(args.Writer, "%s: ", stepMsg)
218+
color.New(color.FgRed).Fprintf(args.Writer, "Fail\n")
219+
return err
220+
}
221+
222+
fmt.Fprintf(args.Writer, "%s: ", stepMsg)
223+
color.New(color.FgGreen).Fprintf(args.Writer, "Success\n")
224+
225+
return nil
226+
}
227+
228+
func profileInstall(args *ProfileInstallArgs) error {
229+
230+
cmd := exec.Command(
231+
"nix", "profile", "install",
210232
"--profile", args.ProfilePath,
211233
"--impure", // Needed to allow flags from environment to be used.
212234
FlakeNixpkgs(args.NixpkgsCommit)+"#"+args.Package,
213235
)
214236
cmd.Args = append(cmd.Args, ExperimentalFlags()...)
237+
if args.Priority != "" {
238+
cmd.Args = append(cmd.Args, "--priority", args.Priority)
239+
}
215240
cmd.Args = append(cmd.Args, args.ExtraFlags...)
216241

217242
cmd.Env = DefaultEnv()
218-
cmd.Stdout = &PackageInstallWriter{args.Writer}
219-
var stderr bytes.Buffer
220-
cmd.Stderr = io.MultiWriter(&stderr, cmd.Stdout)
243+
writer := &PackageInstallWriter{args.Writer}
244+
var stdout, stderr bytes.Buffer
245+
cmd.Stdout = io.MultiWriter(&stdout, writer)
246+
cmd.Stderr = io.MultiWriter(&stderr, writer)
221247

222248
if err := cmd.Run(); err != nil {
223249
if strings.Contains(stderr.String(), "does not provide attribute") {
224250
return ErrPackageNotFound
225251
}
226252

227-
fmt.Fprintf(args.Writer, "%s: ", stepMsg)
228-
color.New(color.FgRed).Fprintf(args.Writer, "Fail\n")
253+
// If two packages being installed seek to install two nix packages (could be themselves,
254+
// or could be a dependency) that have the same binary name,
255+
// then `nix profile install` will fail with `conflicting packages` error (as of nix version 2.14.0)
256+
//
257+
// An example error message looks like the following:
258+
// error: files '/nix/store/spgr12gk13af8flz7akbs18fj4whqss2-bundler-2.4.5/bin/bundle' and
259+
// '/nix/store/l4wmx8lfn6hlcfmbyhmksm024f8hixm1-ruby-3.1.2/bin/bundle' have the same priority 5;
260+
// use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' or type 'nix profile install --help'
261+
// if using 'nix profile' to find out howto change the priority of one of the conflicting packages
262+
// (0 being the highest priority)
263+
//
264+
// However, for the purposes of starting a shell with these packages, nix flakes will give
265+
// precedence to the later package. We enable similar functionality by increasing the priority
266+
// of any package being installed and conflicting with a previously installed package: the
267+
// package being installed later "wins".
268+
isConflictingPackagesError := strings.Contains(stdout.String(), "conflicting packages") ||
269+
strings.Contains(stderr.String(), "conflicting packages")
270+
if isConflictingPackagesError && args.Priority != "0" {
271+
272+
priority := args.Priority
273+
if priority == "" {
274+
priority = "5" // 5 is the default priority in `nix profile`
275+
}
276+
277+
intPriority, strconvErr := strconv.Atoi(priority)
278+
if strconvErr != nil {
279+
debug.Log(
280+
"Error: falling back to regular error handling logic due to strconv.Atoi error: %s",
281+
strconvErr)
282+
// fallthrough to the regular error handling logic
283+
} else {
284+
// to give higher priority, we need to assign a lower priority number
285+
args.Priority = strconv.Itoa(intPriority - 1)
286+
debug.Log("Re-trying nix profile install with priority %s for package %s\n",
287+
args.Priority,
288+
args.Package,
289+
)
290+
return profileInstall(args)
291+
}
292+
}
293+
229294
return errors.Wrapf(err, "Command: %s", cmd)
230295
}
231-
232-
fmt.Fprintf(args.Writer, "%s: ", stepMsg)
233-
color.New(color.FgGreen).Fprintf(args.Writer, "Success\n")
234-
235296
return nil
236297
}
237298

internal/nix/writer.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ package nix
33
import (
44
"io"
55
"strings"
6+
7+
"go.jetpack.io/devbox/internal/debug"
68
)
79

8-
var packageInstallIgnore = []string{
9-
`replacing old 'devbox-development'`,
10-
`installing 'devbox-development'`,
10+
// packageInstallIgnore will skip lines that have the strings in the keys of this map.
11+
// The boolean values inform the writer whether to log the line to debug.Log.
12+
var packageInstallIgnore = map[string]bool{
13+
`replacing old 'devbox-development'`: false,
14+
`installing 'devbox-development'`: false,
15+
`conflicting packages`: true,
1116
}
1217

1318
type PackageInstallWriter struct {
@@ -28,8 +33,11 @@ func (fw *PackageInstallWriter) Write(p []byte) (n int, err error) {
2833
}
2934

3035
func (*PackageInstallWriter) ignore(line string) bool {
31-
for _, filter := range packageInstallIgnore {
36+
for filter, shouldLog := range packageInstallIgnore {
3237
if strings.Contains(line, filter) {
38+
if shouldLog {
39+
debug.Log("Hiding output for user: %s", line)
40+
}
3341
return true
3442
}
3543
}

0 commit comments

Comments
 (0)