Skip to content

[runx] Proof of concept for runx integration #1524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/cli-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ jobs:
go-version-file: ./go.mod
cache: false

- name: Install devbox
uses: jetpack-io/[email protected]
with:
enable-cache: true
# This can be reanabled once released version supports runx
# - name: Install devbox
# uses: jetpack-io/[email protected]
# with:
# enable-cache: true

- name: Mount golang cache
uses: actions/cache@v3
Expand All @@ -82,7 +83,8 @@ jobs:
~/go/pkg
key: go-${{ runner.os }}-${{ hashFiles('go.sum') }}

- run: devbox run fmt
# Use main devbox for now to ensure it supports runx
- run: go run ./cmd/devbox run fmt

- name: golangci-lint
uses: golangci/[email protected]
Expand Down
6 changes: 3 additions & 3 deletions devbox.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"packages": [
"[email protected]",
"go@latest"
"go@latest",
"runx:golangci/golangci-lint@latest"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be preferable to include a source reference. In the future, runx may support github, gitlab, sourcehut, custom domain etc.

runx: github.com/golangci/golangci-lint@latest

],
"env": {
"GOENV": "off",
Expand All @@ -20,8 +20,8 @@
"GOOS=linux GOARCH=arm64 go build -o dist/devbox-linux-arm64 ./cmd/devbox"
],
"code": "code .",
"lint": "golangci-lint run",
"fmt": "scripts/gofumpt.sh",
"lint": "golangci-lint run",
"test": "go test -race -cover ./...",
"update-examples": "devbox run build && go run testscripts/testrunner/updater/main.go"
}
Expand Down
20 changes: 2 additions & 18 deletions devbox.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,8 @@
}
}
},
"[email protected]": {
"last_modified": "2023-05-01T16:53:22Z",
"resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#golangci-lint",
"version": "1.52.2",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/x2l9ljvl5fzffc7vggjk5yksrka9yra4-golangci-lint-1.52.2"
},
"aarch64-linux": {
"store_path": "/nix/store/k905aab7fj7dvf2hhsbb3mmizk052ad7-golangci-lint-1.52.2"
},
"x86_64-darwin": {
"store_path": "/nix/store/imxj7162whh9d56zpk1lzs1b3iw6wzp3-golangci-lint-1.52.2"
},
"x86_64-linux": {
"store_path": "/nix/store/nfhrynpdjd5yamz5snv2c377v1j9jdmx-golangci-lint-1.52.2"
}
}
"runx:golangci/golangci-lint@latest": {
"resolved": "runx:golangci/golangci-lint@latest"
}
}
}
36 changes: 31 additions & 5 deletions internal/devpkg/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/cuecfg"
"go.jetpack.io/devbox/internal/devconfig"
"go.jetpack.io/devbox/internal/devpkg/pkgtype"
"go.jetpack.io/devbox/internal/lock"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/plugins"
Expand Down Expand Up @@ -93,6 +94,9 @@ func newPackage(raw string, isInstallable bool, locker lock.Locker) *Package {
normalizedURL += "#" + pkgURL.Fragment
}
pkgURL, _ = url.Parse(normalizedURL)
} else if pkgURL.Scheme == pkgtype.RunXScheme {
// THIS IS A HACK. These are not URLs and should not be treated as such
pkgURL.Path = pkgURL.Opaque
}

return &Package{URL: *pkgURL, lockfile: locker, Raw: raw, isInstallable: isInstallable}
Expand All @@ -107,11 +111,13 @@ func (p *Package) isLocal() bool {
}

// IsDevboxPackage specifies whether this package is a devbox package. Devbox
// packages have the format `canonicalName@version`and can be resolved by devbox
// search. This also returns true for legacy packages which are just an
// attribute path. An explicit flake reference is _not_ a devbox package.
// packages have the format `canonicalName@version`and can be resolved by
// lockfile.Resolve (including runx packages)
// This also returns true for legacy packages which are just
// an attribute path. An explicit flake reference is _not_ a devbox package.
// TODO: Consider renaming to IsResolvable
func (p *Package) IsDevboxPackage() bool {
return p.Scheme == ""
return p.Scheme == "" || p.IsRunX()
}

// isGithub specifies whether this Package is referenced by a remote flake
Expand Down Expand Up @@ -401,7 +407,7 @@ func (p *Package) CanonicalName() string {
if !p.IsDevboxPackage() {
return ""
}
name, _, _ := strings.Cut(p.Path, "@")
name, _, _ := strings.Cut(p.Raw, "@")
return name
}

Expand Down Expand Up @@ -519,3 +525,23 @@ func (p *Package) EnsureUninstallableIsInLockfile() error {
_, err := p.lockfile.Resolve(p.Raw)
return err
}

func (p *Package) IsRunX() bool {
return pkgtype.IsRunX(p.Raw)
}

func (p *Package) IsNix() bool {
return IsNix(p, 0)
}

func (p *Package) RunXPath() string {
return strings.TrimPrefix(p.Raw, pkgtype.RunXPrefix)
}

func IsNix(p *Package, _ int) bool {
return !p.IsRunX()
}

func IsRunX(p *Package, _ int) bool {
return p.IsRunX()
}
26 changes: 26 additions & 0 deletions internal/devpkg/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,29 @@ func TestStorePathParts(t *testing.T) {
})
}
}

func TestCanonicalName(t *testing.T) {
tests := []struct {
pkgName string
expectedName string
}{
{"go", "go"},
{"go@latest", "go"},
{"[email protected]", "go"},
{"runx:golangci/golangci-lint@latest", "runx:golangci/golangci-lint"},
{"runx:golangci/[email protected]", "runx:golangci/golangci-lint"},
{"runx:golangci/golangci-lint", "runx:golangci/golangci-lint"},
{"github:NixOS/nixpkgs/12345", ""},
{"path:/to/my/file", ""},
}

for _, tt := range tests {
t.Run(tt.pkgName, func(t *testing.T) {
pkg := PackageFromString(tt.pkgName, nil)
got := pkg.CanonicalName()
if got != tt.expectedName {
t.Errorf("Expected canonical name %q, but got %q", tt.expectedName, got)
}
})
}
}
12 changes: 12 additions & 0 deletions internal/devpkg/pkgtype/runx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pkgtype

import "strings"

const (
RunXScheme = "runx"
RunXPrefix = RunXScheme + ":"
)

func IsRunX(s string) bool {
return strings.HasPrefix(s, RunXPrefix)
}
4 changes: 4 additions & 0 deletions internal/devpkg/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
)

func (p *Package) ValidateExists() (bool, error) {
if p.IsRunX() {
// TODO implement runx validation
return true, nil
}
if p.isVersioned() && p.version() == "" {
return false, usererr.New("No version specified for %q.", p.Path)
}
Expand Down
24 changes: 24 additions & 0 deletions internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"go.jetpack.io/devbox/internal/searcher"
"go.jetpack.io/devbox/internal/shellgen"
"go.jetpack.io/devbox/internal/telemetry"
"go.jetpack.io/pkg/sandbox/runx"

"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/cmdutil"
Expand Down Expand Up @@ -747,6 +748,7 @@ func (d *Devbox) StartProcessManager(
// Note that the shellrc.tmpl template (which sources this environment) does
// some additional processing. The computeNixEnv environment won't necessarily
// represent the final "devbox run" or "devbox shell" environments.
// TODO: Rename to computeDevboxEnv?
func (d *Devbox) computeNixEnv(ctx context.Context, usePrintDevEnvCache bool) (map[string]string, error) {
defer trace.StartRegion(ctx, "computeNixEnv").End()

Expand Down Expand Up @@ -874,6 +876,12 @@ func (d *Devbox) computeNixEnv(ctx context.Context, usePrintDevEnvCache bool) (m
})
debug.Log("PATH after filtering with buildInputs (%v) is: %s", buildInputs, nixEnvPath)

runXPaths, err := d.RunXPaths()
if err != nil {
return nil, err
}
nixEnvPath = envpath.JoinPathLists(nixEnvPath, runXPaths)

pathStack := envpath.Stack(env, originalEnv)
pathStack.Push(env, d.projectDirHash(), nixEnvPath, d.preservePathStack)
env["PATH"] = pathStack.Path(env)
Expand Down Expand Up @@ -978,6 +986,9 @@ func (d *Devbox) HasDeprecatedPackages() bool {
}

func (d *Devbox) findPackageByName(name string) (*devpkg.Package, error) {
if name == "" {
return nil, errors.New("package name cannot be empty")
}
results := map[*devpkg.Package]bool{}
for _, pkg := range d.configPackages() {
if pkg.Raw == name || pkg.CanonicalName() == name {
Expand Down Expand Up @@ -1194,3 +1205,16 @@ func (d *Devbox) PluginManager() *plugin.Manager {
func (d *Devbox) Lockfile() *lock.File {
return d.lockfile
}

func (d *Devbox) RunXPaths() (string, error) {
packages := lo.Filter(d.InstallablePackages(), devpkg.IsRunX)
paths := []string{}
for _, pkg := range packages {
p, err := runx.Install(pkg.RunXPath())
if err != nil {
return "", err
}
paths = append(paths, p...)
}
return envpath.JoinPathLists(paths...), nil
}
32 changes: 29 additions & 3 deletions internal/impl/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.jetpack.io/devbox/internal/devpkg"
"go.jetpack.io/devbox/internal/nix/nixprofile"
"go.jetpack.io/devbox/internal/shellgen"
"go.jetpack.io/pkg/sandbox/runx"

"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/debug"
Expand Down Expand Up @@ -66,6 +67,7 @@ func (d *Devbox) Add(ctx context.Context, platforms, excludePlatforms []string,
// match.
found, _ := d.findPackageByName(pkg.CanonicalName())
if found != nil {
ux.Finfo(d.stderr, "Replacing package %q in devbox.json\n", found.Raw)
if err := d.Remove(ctx, found.Raw); err != nil {
return err
}
Expand Down Expand Up @@ -234,6 +236,10 @@ func (d *Devbox) ensurePackagesAreInstalled(ctx context.Context, mode installMod
return err
}

if err := d.InstallRunXPackages(); err != nil {
return err
}

if err := shellgen.GenerateForPrintEnv(ctx, d); err != nil {
return err
}
Expand Down Expand Up @@ -307,6 +313,9 @@ func (d *Devbox) syncPackagesToProfile(ctx context.Context, mode installMode) er
return err
}

// Remove non-nix packages from the list
packages = lo.Filter(packages, devpkg.IsNix)

if err := devpkg.FillNarInfoCache(ctx, packages...); err != nil {
return err
}
Expand Down Expand Up @@ -407,7 +416,7 @@ func (d *Devbox) addPackagesToProfile(ctx context.Context, pkgs []*devpkg.Packag

profileDir, err := d.profilePath()
if err != nil {
return err
return fmt.Errorf("error getting profile path: %w", err)
}

total := len(pkgs)
Expand All @@ -416,14 +425,14 @@ func (d *Devbox) addPackagesToProfile(ctx context.Context, pkgs []*devpkg.Packag

stepMsg := fmt.Sprintf("[%d/%d] %s", stepNum, total, pkg)

if err := nixprofile.ProfileInstall(ctx, &nixprofile.ProfileInstallArgs{
if err = nixprofile.ProfileInstall(ctx, &nixprofile.ProfileInstallArgs{
CustomStepMessage: stepMsg,
Lockfile: d.lockfile,
Package: pkg.Raw,
ProfilePath: profileDir,
Writer: d.stderr,
}); err != nil {
return err
return fmt.Errorf("error installing package %s: %w", pkg, err)
}
}

Expand Down Expand Up @@ -463,3 +472,20 @@ func resetProfileDirForFlakes(profileDir string) (err error) {

return errors.WithStack(os.Remove(profileDir))
}

func (d *Devbox) InstallRunXPackages() error {
for _, pkg := range d.InstallablePackages() {
if pkg.IsRunX() {
// TODO: Once resolve is implemented, we use whatever version is in the lockfile.
if _, err := d.lockfile.Resolve(pkg.Raw); err != nil {
return err
}
_, err := runx.Install(pkg.RunXPath())
if err != nil {
return fmt.Errorf("error installing runx package %s: %w", pkg, err)
}

}
}
return nil
}
8 changes: 7 additions & 1 deletion internal/lock/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/pkg/errors"
"github.com/samber/lo"
"go.jetpack.io/devbox/internal/devpkg/pkgtype"
"go.jetpack.io/devbox/internal/searcher"

"go.jetpack.io/devbox/internal/cuecfg"
Expand Down Expand Up @@ -69,7 +70,12 @@ func (f *File) Resolve(pkg string) (*Package, error) {
if !hasEntry || entry.Resolved == "" {
locked := &Package{}
var err error
if _, _, versioned := searcher.ParseVersionedPackage(pkg); versioned {
if pkgtype.IsRunX(pkg) {
// TODO implement runx resolution. This can be done by reading the releases.json file
locked = &Package{
Resolved: pkg,
}
} else if _, _, versioned := searcher.ParseVersionedPackage(pkg); versioned {
locked, err = f.FetchResolvedPackage(pkg)
if err != nil {
return nil, err
Expand Down
8 changes: 6 additions & 2 deletions internal/nix/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"fmt"
"os"
"os/exec"
"strings"

"github.com/pkg/errors"
"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/debug"
)

Expand All @@ -30,6 +30,10 @@ func (i *Info) String() string {
}

func Search(url string) (map[string]*Info, error) {
if strings.HasPrefix(url, "runx:") {
// TODO implement runx search
return map[string]*Info{}, nil
}
return searchSystem(url, "")
}

Expand Down Expand Up @@ -98,7 +102,7 @@ func searchSystem(url, system string) (map[string]*Info, error) {
out, err := cmd.Output()
if err != nil {
// for now, assume all errors are invalid packages.
return nil, usererr.NewExecError(err)
return nil, fmt.Errorf("error searching for pkg %s: %w", url, err)
}
return parseSearchResults(out), nil
}
Loading