Skip to content

[Packages] Introduce the concept of Installable packages #1357

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 6 commits into from
Aug 11, 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
19 changes: 14 additions & 5 deletions internal/devconfig/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ type Packages struct {
func (pkgs *Packages) VersionedNames() []string {
result := make([]string, 0, len(pkgs.Collection))
for _, p := range pkgs.Collection {
name := p.name
if p.Version != "" {
name += "@" + p.Version
}
result = append(result, name)
result = append(result, p.VersionedName())
}
return result
}
Expand Down Expand Up @@ -154,6 +150,19 @@ func NewPackage(name string, values map[string]any) Package {
}
}

func (p *Package) VersionedName() string {
name := p.name
if p.Version != "" {
name += "@" + p.Version
}
return name
}

func (p *Package) IsEnabledOnPlatform() bool {
// TODO savil. Next PR will update this implementation
return true
}

func (p *Package) UnmarshalJSON(data []byte) error {
// First, attempt to unmarshal as a version-only string
var version string
Expand Down
25 changes: 24 additions & 1 deletion internal/devpkg/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.jetpack.io/devbox/internal/boxcli/featureflag"
"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/cuecfg"
"go.jetpack.io/devbox/internal/devconfig"
"go.jetpack.io/devbox/internal/lock"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/vercheck"
Expand Down Expand Up @@ -46,6 +47,9 @@ type Package struct {
// example: github:nixos/nixpkgs/5233fd2ba76a3accb5aaa999c00509a11fd0793c#hello
Raw string

// isInstallable is true if the package may be enabled on the current platform.
isInstallable bool

normalizedPackageAttributePathCache string // memoized value from normalizedPackageAttributePath()
}

Expand All @@ -59,9 +63,22 @@ func PackageFromStrings(rawNames []string, l lock.Locker) []*Package {
return packages
}

func PackagesFromConfig(config *devconfig.Config, l lock.Locker) []*Package {
result := []*Package{}
for _, pkg := range config.Packages.Collection {
result = append(result, newPackage(pkg.VersionedName(), pkg.IsEnabledOnPlatform(), l))
}
return result
}

// PackageFromString constructs Package from the raw name provided.
// The raw name corresponds to a devbox package from the devbox.json config.
func PackageFromString(raw string, locker lock.Locker) *Package {
// Packages are installable by default.
return newPackage(raw, true /*isInstallable*/, locker)
}

func newPackage(raw string, isInstallable bool, locker lock.Locker) *Package {
// TODO: We should handle this error
// TODO: URL might not be best representation since most packages are not urls
pkgURL, _ := url.Parse(raw)
Expand All @@ -79,7 +96,7 @@ func PackageFromString(raw string, locker lock.Locker) *Package {
pkgURL, _ = url.Parse(normalizedURL)
}

return &Package{URL: *pkgURL, lockfile: locker, Raw: raw}
return &Package{URL: *pkgURL, lockfile: locker, Raw: raw, isInstallable: isInstallable}
}

// isLocal specifies whether this package is a local flake.
Expand Down Expand Up @@ -146,6 +163,12 @@ func (p *Package) URLForFlakeInput() string {
return p.urlWithoutFragment()
}

// IsInstallable returns whether this package is installable. Not to be confused
// with the Installable() method which returns the corresponding nix concept.
func (p *Package) IsInstallable() bool {
return p.isInstallable
}

// Installable for this package. Installable is a nix concept defined here:
// https://nixos.org/manual/nix/stable/command-ref/new-cli/nix.html#installables
func (p *Package) Installable() (string, error) {
Expand Down
49 changes: 28 additions & 21 deletions internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (d *Devbox) Config() *devconfig.Config {
}

func (d *Devbox) ConfigHash() (string, error) {
pkgHashes := lo.Map(d.PackagesAsInputs(), func(i *devpkg.Package, _ int) string { return i.Hash() })
pkgHashes := lo.Map(d.configPackages(), func(i *devpkg.Package, _ int) string { return i.Hash() })
includeHashes := lo.Map(d.Includes(), func(i plugin.Includable, _ int) string { return i.Hash() })
h, err := d.cfg.Hash()
if err != nil {
Expand Down Expand Up @@ -400,7 +400,7 @@ func (d *Devbox) GenerateDevcontainer(ctx context.Context, generateOpts devopt.G
Path: devContainerPath,
RootUser: generateOpts.RootUser,
IsDevcontainer: true,
Pkgs: d.Packages(),
Pkgs: d.PackageNames(),
LocalFlakeDirs: d.getLocalFlakesDirs(),
}

Expand Down Expand Up @@ -439,7 +439,7 @@ func (d *Devbox) GenerateDockerfile(ctx context.Context, generateOpts devopt.Gen
Path: d.projectDir,
RootUser: generateOpts.RootUser,
IsDevcontainer: false,
Pkgs: d.Packages(),
Pkgs: d.PackageNames(),
LocalFlakeDirs: d.getLocalFlakesDirs(),
}

Expand Down Expand Up @@ -499,10 +499,7 @@ func (d *Devbox) saveCfg() error {
}

func (d *Devbox) Services() (services.Services, error) {
pluginSvcs, err := d.pluginManager.GetServices(
d.PackagesAsInputs(),
d.cfg.Include,
)
pluginSvcs, err := d.pluginManager.GetServices(d.InstallablePackages(), d.cfg.Include)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -840,7 +837,7 @@ func (d *Devbox) computeNixEnv(ctx context.Context, usePrintDevEnvCache bool) (m
// We still need to be able to add env variables to non-service binaries
// (e.g. ruby). This would involve understanding what binaries are associated
// to a given plugin.
pluginEnv, err := d.pluginManager.Env(d.PackagesAsInputs(), d.cfg.Include, env)
pluginEnv, err := d.pluginManager.Env(d.InstallablePackages(), d.cfg.Include, env)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -942,19 +939,30 @@ func (d *Devbox) nixFlakesFilePath() string {
return filepath.Join(d.projectDir, ".devbox/gen/flake/flake.nix")
}

// Packages returns the list of Packages to be installed in the nix shell.
func (d *Devbox) Packages() []string {
// ConfigPackageNames returns the package names as defined in devbox.json
func (d *Devbox) PackageNames() []string {
// TODO savil: centralize implementation by calling d.configPackages and getting pkg.Raw
// Skipping for now to avoid propagating the error value.
return d.cfg.Packages.VersionedNames()
}

func (d *Devbox) PackagesAsInputs() []*devpkg.Package {
return devpkg.PackageFromStrings(d.Packages(), d.lockfile)
// configPackages returns the packages that are defined in devbox.json
// NOTE: the return type is different from devconfig.Packages
func (d *Devbox) configPackages() []*devpkg.Package {
return devpkg.PackagesFromConfig(d.cfg, d.lockfile)
}

// InstallablePackages returns the packages that are to be installed
func (d *Devbox) InstallablePackages() []*devpkg.Package {
return lo.Filter(d.configPackages(), func(pkg *devpkg.Package, _ int) bool {
return pkg.IsInstallable()
})
}

// AllPackages returns user packages and plugin packages concatenated in
// correct order
func (d *Devbox) AllPackages() ([]*devpkg.Package, error) {
userPackages := d.PackagesAsInputs()
// InstallableAndPluginPackages returns installable user packages and plugin
// packages concatenated in correct order
func (d *Devbox) AllInstallablePackages() ([]*devpkg.Package, error) {
userPackages := d.InstallablePackages()
pluginPackages, err := d.PluginManager().PluginPackages(userPackages)
if err != nil {
return nil, err
Expand All @@ -976,7 +984,7 @@ func (d *Devbox) Includes() []plugin.Includable {
}

func (d *Devbox) HasDeprecatedPackages() bool {
for _, pkg := range d.PackagesAsInputs() {
for _, pkg := range d.configPackages() {
if pkg.IsLegacy() {
return true
}
Expand All @@ -986,10 +994,9 @@ func (d *Devbox) HasDeprecatedPackages() bool {

func (d *Devbox) findPackageByName(name string) (string, error) {
results := map[string]bool{}
for _, pkg := range d.cfg.Packages.VersionedNames() {
i := devpkg.PackageFromString(pkg, d.lockfile)
if i.String() == name || i.CanonicalName() == name {
results[i.String()] = true
for _, pkg := range d.configPackages() {
if pkg.String() == name || pkg.CanonicalName() == name {
results[pkg.String()] = true
}
}
if len(results) > 1 {
Expand Down
2 changes: 1 addition & 1 deletion internal/impl/flakes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (d *Devbox) getLocalFlakesDirs() []string {
localFlakeDirs := []string{}

// searching through installed packages to get location of local flakes
for _, pkg := range d.Packages() {
for _, pkg := range d.PackageNames() {
// filtering local flakes packages
if strings.HasPrefix(pkg, "path:") {
pkgDirAndName, _ := strings.CutPrefix(pkg, "path:")
Expand Down
2 changes: 1 addition & 1 deletion internal/impl/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
const currentGlobalProfile = "default"

func (d *Devbox) PrintGlobalList() error {
for _, p := range d.cfg.Packages.VersionedNames() {
for _, p := range d.PackageNames() {
fmt.Fprintf(d.writer, "* %s\n", p)
}
return nil
Expand Down
11 changes: 5 additions & 6 deletions internal/impl/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
// names of added packages (even if they are already in config). We use this
// to know the exact name to mark as allowed insecure later on.
addedPackageNames := []string{}
existingPackageNames := d.cfg.Packages.VersionedNames()
existingPackageNames := d.PackageNames()
for _, pkg := range pkgs {
// If exact versioned package is already in the config, skip.
if slices.Contains(existingPackageNames, pkg.Versioned()) {
Expand Down Expand Up @@ -197,7 +197,7 @@ func (d *Devbox) ensurePackagesAreInstalled(ctx context.Context, mode installMod
}

// Create plugin directories first because packages might need them
for _, pkg := range d.PackagesAsInputs() {
for _, pkg := range d.InstallablePackages() {
if err := d.PluginManager().Create(pkg); err != nil {
return err
}
Expand Down Expand Up @@ -375,7 +375,7 @@ func (d *Devbox) tidyProfile(ctx context.Context) error {
// pendingPackagesForInstallation returns a list of packages that are in
// devbox.json or global devbox.json but are not yet installed in the nix
// profile. It maintains the order of packages as specified by
// Devbox.packages() (higher priority first)
// Devbox.AllPackages() (higher priority first)
func (d *Devbox) pendingPackagesForInstallation(ctx context.Context) ([]*devpkg.Package, error) {
defer trace.StartRegion(ctx, "pendingPackages").End()

Expand All @@ -389,7 +389,7 @@ func (d *Devbox) pendingPackagesForInstallation(ctx context.Context) ([]*devpkg.
if err != nil {
return nil, err
}
packages, err := d.AllPackages()
packages, err := d.AllInstallablePackages()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -428,8 +428,7 @@ func (d *Devbox) extraPackagesInProfile(ctx context.Context) ([]*nixprofile.NixP
if err != nil {
return nil, err
}
devboxInputs := d.PackagesAsInputs()

devboxInputs := d.InstallablePackages()
if len(devboxInputs) == len(profileItems) {
// Optimization: skip comparison if number of packages are the same. This only works
// because we assume that all packages in `devbox.json` have just been added to the
Expand Down
2 changes: 1 addition & 1 deletion internal/impl/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (d *Devbox) inputsToUpdate(pkgs ...string) ([]*devpkg.Package, error) {
pkgsToUpdate = append(pkgsToUpdate, found)
}
if len(pkgsToUpdate) == 0 {
pkgsToUpdate = d.Packages()
pkgsToUpdate = d.PackageNames()
}

return devpkg.PackageFromStrings(pkgsToUpdate, d.lockfile), nil
Expand Down
2 changes: 1 addition & 1 deletion internal/lock/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package lock
type devboxProject interface {
ConfigHash() (string, error)
NixPkgsCommitHash() string
Packages() []string
PackageNames() []string
ProjectDir() string
}

Expand Down
2 changes: 1 addition & 1 deletion internal/lock/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func IsLegacyPackage(pkg string) bool {
// Tidy ensures that the lockfile has the set of packages corresponding to the devbox.json config.
// It gets rid of older packages that are no longer needed.
func (f *File) Tidy() {
f.Packages = lo.PickByKeys(f.Packages, f.devboxProject.Packages())
f.Packages = lo.PickByKeys(f.Packages, f.devboxProject.PackageNames())
}

// IsUpToDateAndInstalled returns true if the lockfile is up to date and the
Expand Down
15 changes: 11 additions & 4 deletions internal/nix/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,19 @@ func isRoot() bool {
return os.Geteuid() == 0
}

func EnsureNixInstalled(writer io.Writer, withDaemonFunc func() *bool) error {
func EnsureNixInstalled(writer io.Writer, withDaemonFunc func() *bool) (err error) {
defer func() {
if err == nil {
// call System to ensure its value is internally cached so we can rely on MustGetSystem
_, err = System()
}
}()

if BinaryInstalled() {
return nil
}
if dirExists() {
if err := SourceNixEnv(); err != nil {
if err = SourceNixEnv(); err != nil {
return err
} else if BinaryInstalled() {
return nil
Expand All @@ -122,12 +129,12 @@ func EnsureNixInstalled(writer io.Writer, withDaemonFunc func() *bool) error {
fmt.Scanln()
}

if err := Install(writer, withDaemonFunc()); err != nil {
if err = Install(writer, withDaemonFunc()); err != nil {
return err
}

// Source again
if err := SourceNixEnv(); err != nil {
if err = SourceNixEnv(); err != nil {
return err
}

Expand Down
9 changes: 9 additions & 0 deletions internal/nix/nix.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,17 @@ func ExperimentalFlags() []string {
}
}

// TODO: rename to System
func MustGetSystem() string {
if cachedSystem == "" {
panic("MustGetSystem called before being initialized by System")
}
return cachedSystem
}

var cachedSystem string

// TODO: rename to ComputeSystem
func System() (string, error) {
// For Savil to debug "remove nixpkgs" feature. The Search api lacks x86-darwin info.
// So, I need to fake that I am x86-linux and inspect the output in generated devbox.lock
Expand Down
2 changes: 1 addition & 1 deletion internal/plugin/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Manager struct {
}

type devboxProject interface {
Packages() []string
PackageNames() []string
ProjectDir() string
}

Expand Down
2 changes: 1 addition & 1 deletion internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (m *Manager) createFile(
"DevboxDirRoot": filepath.Join(m.ProjectDir(), devboxDirName),
"DevboxProfileDefault": filepath.Join(m.ProjectDir(), nix.ProfilePath),
"PackageAttributePath": attributePath,
"Packages": m.Packages(),
"Packages": m.PackageNames(),
"System": system,
"URLForInput": urlForInput,
"Virtenv": filepath.Join(virtenvPath, name),
Expand Down
2 changes: 1 addition & 1 deletion internal/shellgen/flake_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func newFlakePlan(ctx context.Context, devbox devboxer) (*flakePlan, error) {
}
}

packages, err := devbox.AllPackages()
packages, err := devbox.AllInstallablePackages()
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions internal/shellgen/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const HooksFilename = ".hooks"
type devboxer interface {
Config() *devconfig.Config
Lockfile() *lock.File
PackagesAsInputs() []*devpkg.Package
AllPackages() ([]*devpkg.Package, error)
AllInstallablePackages() ([]*devpkg.Package, error)
InstallablePackages() []*devpkg.Package
PluginManager() *plugin.Manager
ProjectDir() string
}
Expand All @@ -45,7 +45,7 @@ func WriteScriptsToFiles(devbox devboxer) error {

// Write all hooks to a file.
written := map[string]struct{}{} // set semantics; value is irrelevant
pluginHooks, err := plugin.InitHooks(devbox.PackagesAsInputs(), devbox.ProjectDir())
pluginHooks, err := plugin.InitHooks(devbox.InstallablePackages(), devbox.ProjectDir())
if err != nil {
return errors.WithStack(err)
}
Expand Down