Skip to content

Commit 9a50fac

Browse files
authored
[pkgcfg] Create new pkgckg that can load local config (#253)
## Summary Creates a new package that allows loading of local configs. This will allow for rapid testing and iteration of package configuration before we read them remotely. ## How was it tested? Created dummy go config and ran: ``` DEVBOX_FEATURE_PKG_CONFIG=1 DEVBOX_LOCAL_PKG_CONFIG=~/dev/devbox ./dist/devbox shell echo $FOO ./.devbox/test-file.sh ```
1 parent 402a343 commit 9a50fac

File tree

5 files changed

+136
-10
lines changed

5 files changed

+136
-10
lines changed

boxcli/featureflag/feature.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package featureflag
33
import (
44
"os"
55
"strconv"
6+
7+
"go.jetpack.io/devbox/debug"
68
)
79

810
type feature struct {
@@ -29,6 +31,7 @@ func (f *feature) Enabled() bool {
2931
return false
3032
}
3133
if on, _ := strconv.ParseBool(os.Getenv("DEVBOX_FEATURE_" + f.name)); on {
34+
debug.Log("Feature %q enabled via environment variable.", f.name)
3235
return true
3336
}
3437
return f.enabled

devbox.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import (
1616
"github.com/fatih/color"
1717
"github.com/pkg/errors"
1818
"github.com/samber/lo"
19+
"go.jetpack.io/devbox/boxcli/featureflag"
1920
"go.jetpack.io/devbox/cuecfg"
2021
"go.jetpack.io/devbox/debug"
2122
"go.jetpack.io/devbox/docker"
2223
"go.jetpack.io/devbox/nix"
24+
"go.jetpack.io/devbox/pkgcfg"
2325
"go.jetpack.io/devbox/planner"
2426
"go.jetpack.io/devbox/planner/plansdk"
2527
"golang.org/x/exp/slices"
@@ -200,11 +202,22 @@ func (d *Devbox) Shell() error {
200202
}
201203

202204
nixShellFilePath := filepath.Join(d.srcDir, ".devbox/gen/shell.nix")
203-
shell, err := nix.DetectShell(
205+
206+
opts := []nix.ShellOption{
204207
nix.WithPlanInitHook(strings.Join(plan.ShellInitHook, "\n")),
205208
nix.WithProfile(profileDir),
206209
nix.WithHistoryFile(filepath.Join(d.srcDir, shellHistoryFile)),
207-
)
210+
}
211+
212+
if featureflag.Get(featureflag.PKGConfig).Enabled() {
213+
env, err := pkgcfg.Env(plan.DevPackages)
214+
if err != nil {
215+
return err
216+
}
217+
opts = append(opts, nix.WithEnvVariables(env))
218+
}
219+
220+
shell, err := nix.DetectShell(opts...)
208221
if err != nil {
209222
// Fall back to using a plain Nix shell.
210223
shell = &nix.Shell{}

generate.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
"text/template"
1414

1515
"github.com/pkg/errors"
16+
"go.jetpack.io/devbox/boxcli/featureflag"
1617
"go.jetpack.io/devbox/debug"
18+
"go.jetpack.io/devbox/pkgcfg"
1719
"go.jetpack.io/devbox/planner/plansdk"
1820
)
1921

@@ -48,6 +50,14 @@ func generateForShell(rootPath string, plan *plansdk.ShellPlan) error {
4850
}
4951
}
5052

53+
if featureflag.Get(featureflag.PKGConfig).Enabled() {
54+
for _, pkg := range plan.DevPackages {
55+
if err := pkgcfg.CreateFiles(pkg, rootPath); err != nil {
56+
return err
57+
}
58+
}
59+
}
60+
5161
return nil
5262
}
5363

nix/shell.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
type Shell struct {
3838
name name
3939
binPath string
40+
env []string
4041
userShellrcPath string
4142
planInitHook string
4243

@@ -114,6 +115,14 @@ func WithHistoryFile(historyFile string) ShellOption {
114115
}
115116
}
116117

118+
func WithEnvVariables(envVariables map[string]string) ShellOption {
119+
return func(s *Shell) {
120+
for k, v := range envVariables {
121+
s.env = append(s.env, fmt.Sprintf("%s=%s", k, v))
122+
}
123+
}
124+
}
125+
117126
// rcfilePath returns the absolute path for an rcfile, which is usually in the
118127
// user's home directory. It doesn't guarantee that the file exists.
119128
func rcfilePath(basename string) string {
@@ -134,21 +143,23 @@ func (s *Shell) Run(nixShellFilePath string) error {
134143
// directories that are incompatible.
135144
parentPath := cleanEnvPath(os.Getenv("PATH"), nixProfileDirs)
136145

137-
env := append(
138-
os.Environ(),
146+
env := append(s.env, os.Environ()...)
147+
env = append(
148+
env,
139149
"PARENT_PATH="+parentPath,
140150
"NIX_PROFILES="+strings.Join(nixProfileDirs, " "),
141151

142152
// Prevent the user's shellrc from re-sourcing nix-daemon.sh
143153
// inside the devbox shell.
144154
"__ETC_PROFILE_NIX_SOURCED=1",
145155
)
156+
debug.Log("Running nix-shell with environment: %v", env)
146157

147158
// Launch a fallback shell if we couldn't find the path to the user's
148159
// default shell.
149160
if s.binPath == "" {
150161
cmd := exec.Command("nix-shell", "--pure")
151-
cmd.Args = append(cmd.Args, toKeepArgs(env)...)
162+
cmd.Args = append(cmd.Args, toKeepArgs(env, buildAllowList(s.env))...)
152163
cmd.Args = append(cmd.Args, nixShellFilePath)
153164
cmd.Env = env
154165
cmd.Stdin = os.Stdin
@@ -160,7 +171,7 @@ func (s *Shell) Run(nixShellFilePath string) error {
160171
}
161172

162173
cmd := exec.Command("nix-shell", "--command", s.execCommand(), "--pure")
163-
cmd.Args = append(cmd.Args, toKeepArgs(env)...)
174+
cmd.Args = append(cmd.Args, toKeepArgs(env, buildAllowList(s.env))...)
164175
cmd.Args = append(cmd.Args, nixShellFilePath)
165176
cmd.Env = env
166177
cmd.Stdin = os.Stdin
@@ -374,16 +385,25 @@ var envToKeep = map[string]bool{
374385
"SSL_CERT_FILE": true, // The path to non-Nix SSL certificates (used by some Nix and non-Nix programs).
375386
}
376387

388+
func buildAllowList(allowList []string) map[string]bool {
389+
for _, kv := range allowList {
390+
key, _, _ := strings.Cut(kv, "=")
391+
envToKeep[key] = true
392+
}
393+
return envToKeep
394+
}
395+
377396
// toKeepArgs takes a slice of environment variables in key=value format and
378397
// builds a slice of "--keep" arguments that tell nix-shell which ones to
379398
// keep.
380399
//
381-
// See envToKeep for the full set of kept environment variables.
382-
func toKeepArgs(env []string) []string {
383-
args := make([]string, 0, len(envToKeep)*2)
400+
// See envToKeep for the full set of permanent kept environment variables.
401+
// We also --keep any variables set by package configuration.
402+
func toKeepArgs(env []string, allowList map[string]bool) []string {
403+
args := make([]string, 0, len(allowList)*2)
384404
for _, kv := range env {
385405
key, _, _ := strings.Cut(kv, "=")
386-
if envToKeep[key] {
406+
if allowList[key] {
387407
args = append(args, "--keep", key)
388408
}
389409
}

pkgcfg/pkgcfg.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package pkgcfg
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/pkg/errors"
9+
"go.jetpack.io/devbox/debug"
10+
)
11+
12+
const localPkgConfigPath = "DEVBOX_LOCAL_PKG_CONFIG"
13+
14+
type config struct {
15+
Name string `json:"name"`
16+
Version string `json:"version"`
17+
CreateFiles map[string]string `json:"create_files"`
18+
Env map[string]string `json:"env"`
19+
}
20+
21+
func get(pkg string) (*config, error) {
22+
if configPath := os.Getenv(localPkgConfigPath); configPath != "" {
23+
debug.Log("Using local package config at %q", configPath)
24+
return getLocalConfig(configPath, pkg)
25+
}
26+
return &config{}, nil
27+
}
28+
29+
func getLocalConfig(configPath, pkg string) (*config, error) {
30+
configPath = filepath.Join(configPath, pkg+".json")
31+
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
32+
// We don't need config for all packages and that's fine
33+
return &config{}, nil
34+
}
35+
debug.Log("Reading local package config at %q", configPath)
36+
content, err := os.ReadFile(configPath)
37+
if err != nil {
38+
return nil, errors.WithStack(err)
39+
}
40+
cfg := &config{}
41+
if err = json.Unmarshal(content, cfg); err != nil {
42+
return nil, errors.WithStack(err)
43+
}
44+
return cfg, nil
45+
}
46+
47+
func CreateFiles(pkg, basePath string) error {
48+
cfg, err := get(pkg)
49+
if err != nil {
50+
return err
51+
}
52+
for name, contentPath := range cfg.CreateFiles {
53+
filePath := filepath.Join(basePath, name)
54+
if _, err := os.Stat(filePath); err == nil {
55+
continue
56+
}
57+
content, err := os.ReadFile(filepath.Join(basePath, contentPath))
58+
if err != nil {
59+
return errors.WithStack(err)
60+
}
61+
if err := os.WriteFile(filePath, content, 0744); err != nil {
62+
return errors.WithStack(err)
63+
}
64+
}
65+
return nil
66+
}
67+
68+
func Env(pkgs []string) (map[string]string, error) {
69+
env := map[string]string{}
70+
for _, pkg := range pkgs {
71+
cfg, err := get(pkg)
72+
if err != nil {
73+
return nil, err
74+
}
75+
for k, v := range cfg.Env {
76+
env[k] = v
77+
}
78+
}
79+
return env, nil
80+
}

0 commit comments

Comments
 (0)