Skip to content

Commit 09b885c

Browse files
Merge pull request #287 from CircleCI-Public/fact-120-persist-env-setup
[FACT-120] Persist detected environment to .chunk/config.json
2 parents a468b50 + 08d7f41 commit 09b885c

6 files changed

Lines changed: 228 additions & 116 deletions

File tree

.chunk/config.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,24 @@
3636
"vcs": {
3737
"org": "CircleCI-Public",
3838
"repo": "chunk-cli"
39+
},
40+
"environment": {
41+
"stack": "go",
42+
"setup": [
43+
{
44+
"name": "runtime",
45+
"command": "curl -fsSL https://go.dev/dl/go1.26.2.linux-$(dpkg --print-architecture).tar.gz | sudo tar -C /usr/local -xzf -"
46+
},
47+
{
48+
"name": "system",
49+
"command": "sudo apt-get update \u0026\u0026 sudo apt-get install -y git --no-install-recommends \u0026\u0026 sudo rm -rf /var/lib/apt/lists/*"
50+
},
51+
{
52+
"name": "install",
53+
"command": "go mod download"
54+
}
55+
],
56+
"image": "cimg/go",
57+
"image_version": "1.26.2"
3958
}
4059
}

envbuilder/envbuilder.go

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/BurntSushi/toml"
1616

17+
"github.com/CircleCI-Public/chunk-cli/internal/envspec"
1718
hc "github.com/CircleCI-Public/chunk-cli/internal/httpcl"
1819
)
1920

@@ -39,28 +40,10 @@ const (
3940
toolPytest = "pytest"
4041
)
4142

42-
// Environment describes the detected tech stack and build configuration for a repository.
43-
type Environment struct {
44-
Stack string `json:"stack"`
45-
Install string `json:"install"`
46-
Test string `json:"test"`
47-
SystemDeps []string `json:"system_deps"`
48-
Image string `json:"image"`
49-
ImageVersion string `json:"image_version"`
50-
}
51-
52-
// BinaryPaths returns colon-separated PATH prefixes needed for the detected stack.
53-
// cimg images set these via Docker ENV which e2b does not propagate to SSH sessions.
54-
func (e *Environment) BinaryPaths() string {
55-
switch e.Stack {
56-
case stackGo:
57-
return "/usr/local/go/bin:/home/circleci/go/bin"
58-
case stackJavaScript, stackTypeScript:
59-
return "/home/circleci/.yarn/bin"
60-
default:
61-
return ""
62-
}
63-
}
43+
// Step and Environment are defined in internal/envspec; alias them here so
44+
// existing callers using envbuilder.Step / envbuilder.Environment continue to work.
45+
type Step = envspec.Step
46+
type Environment = envspec.Environment
6447

6548
func fileExists(dir, name string) bool {
6649
_, err := os.Stat(filepath.Join(dir, name))
@@ -451,17 +434,8 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
451434

452435
sb.WriteString("FROM " + env.Image + ":" + env.ImageVersion + "\n")
453436

454-
for _, dep := range env.SystemDeps {
455-
if cmd, ok := extraDepInstalls[dep]; ok {
456-
// cimg/* images run as a non-root user (circleci), so apt-get requires sudo.
457-
if strings.HasPrefix(env.Image, cimgPrefix) {
458-
cmd = strings.ReplaceAll(cmd, "apt-get", "sudo apt-get")
459-
cmd = strings.ReplaceAll(cmd, "rm -rf /var/lib/apt/lists/*", "sudo rm -rf /var/lib/apt/lists/*")
460-
cmd = strings.ReplaceAll(cmd, "locale-gen", "sudo locale-gen")
461-
cmd = strings.ReplaceAll(cmd, "update-locale", "sudo update-locale")
462-
}
463-
sb.WriteString("\nRUN " + cmd + "\n")
464-
}
437+
if sysCmd := env.SetupStep("system"); sysCmd != "" {
438+
sb.WriteString("\nRUN " + sysCmd + "\n")
465439
}
466440

467441
// When a Python project has Rust workspace members (e.g. maturin extensions like
@@ -526,7 +500,9 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
526500
chown = "--chown=circleci:circleci "
527501
}
528502
sb.WriteString("COPY " + chown + "go.mod go.sum ./\n")
529-
sb.WriteString("RUN " + env.Install + "\n")
503+
if installCmd := env.SetupStep("install"); installCmd != "" {
504+
sb.WriteString("RUN " + installCmd + "\n")
505+
}
530506
sb.WriteString("\nCOPY " + chown + ". .\n")
531507
case stackRuby:
532508
// Use the split-COPY pattern (mirrors the Go approach) to avoid
@@ -566,7 +542,9 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
566542
if hasGemspec && fileExists(dir, "lib") {
567543
sb.WriteString("COPY " + chown + "lib/ lib/\n")
568544
}
569-
sb.WriteString("RUN " + env.Install + "\n")
545+
if installCmd := env.SetupStep("install"); installCmd != "" {
546+
sb.WriteString("RUN " + installCmd + "\n")
547+
}
570548
sb.WriteString("\nCOPY " + chown + ". .\n")
571549
default:
572550
if strings.HasPrefix(env.Image, cimgPrefix) {
@@ -596,7 +574,7 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
596574
sdkRe := regexp.MustCompile(`^(\d+)\.`)
597575
tfmRe := regexp.MustCompile(`--framework net(\d+)\.0`)
598576
if sdkM := sdkRe.FindStringSubmatch(env.ImageVersion); len(sdkM) == 2 {
599-
if tfmM := tfmRe.FindStringSubmatch(env.Test); len(tfmM) == 2 {
577+
if tfmM := tfmRe.FindStringSubmatch(env.SetupStep("test")); len(tfmM) == 2 {
600578
sdkMajor, sdkErr := strconv.Atoi(sdkM[1])
601579
tfmMajor, tfmErr := strconv.Atoi(tfmM[1])
602580
if sdkErr == nil && tfmErr == nil && sdkMajor > tfmMajor && tfmMajor >= 6 {
@@ -620,14 +598,16 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
620598
// cache under ~/.gradle/wrapper/dists/, but /home/circleci may not be
621599
// writable in the Docker build context. Redirect GRADLE_USER_HOME to
622600
// /tmp so the wrapper can always unpack and lock its distribution files.
623-
if env.Stack == stackJava && strings.Contains(env.Install, "gradlew") {
601+
if env.Stack == stackJava && strings.Contains(env.SetupStep("install"), "gradlew") {
624602
sb.WriteString("\nENV GRADLE_USER_HOME=/tmp/.gradle\n")
625603
}
626604

627605
// Go and Ruby already emitted their install steps inside the split-COPY
628606
// block above.
629607
if env.Stack != stackGo && env.Stack != stackRuby {
630-
sb.WriteString("\nRUN " + env.Install + "\n")
608+
if installCmd := env.SetupStep("install"); installCmd != "" {
609+
sb.WriteString("\nRUN " + installCmd + "\n")
610+
}
631611
}
632612

633613
// Elixir-specific fixups applied after deps are fetched:
@@ -712,8 +692,8 @@ func dockerfileContent(dir string, env *Environment) string { //nolint:gocyclo
712692
// test them one at a time. $$ is the shell PID, used to give the
713693
// temp file a unique name so concurrent runs don't collide.
714694
sb.WriteString("\nCMD go list ./... > /tmp/mod-pkgs-$$ && go list -deps ./... | grep -Fxf /tmp/mod-pkgs-$$ | while IFS= read -r pkg; do go test \"$pkg\" || exit 1; done\n")
715-
} else {
716-
sb.WriteString("\nCMD " + env.Test + "\n")
695+
} else if testCmd := env.SetupStep("test"); testCmd != "" {
696+
sb.WriteString("\nCMD " + testCmd + "\n")
717697
}
718698

719699
return sb.String()
@@ -1943,11 +1923,33 @@ func DetectEnvironment(ctx context.Context, dir string) (*Environment, error) {
19431923
}
19441924
}
19451925

1926+
var setup []Step
1927+
1928+
var sysCmds []string
1929+
for _, dep := range systemDeps {
1930+
if cmd, ok := extraDepInstalls[dep]; ok {
1931+
if strings.HasPrefix(image, cimgPrefix) {
1932+
cmd = strings.ReplaceAll(cmd, "apt-get", "sudo apt-get")
1933+
cmd = strings.ReplaceAll(cmd, "rm -rf /var/lib/apt/lists/*", "sudo rm -rf /var/lib/apt/lists/*")
1934+
cmd = strings.ReplaceAll(cmd, "locale-gen", "sudo locale-gen")
1935+
cmd = strings.ReplaceAll(cmd, "update-locale", "sudo update-locale")
1936+
}
1937+
sysCmds = append(sysCmds, cmd)
1938+
}
1939+
}
1940+
if len(sysCmds) > 0 {
1941+
setup = append(setup, Step{Name: "system", Command: strings.Join(sysCmds, " && ")})
1942+
}
1943+
if install != "" && install != stackUnknown {
1944+
setup = append(setup, Step{Name: "install", Command: install})
1945+
}
1946+
if test != "" && test != stackUnknown {
1947+
setup = append(setup, Step{Name: "test", Command: test})
1948+
}
1949+
19461950
return &Environment{
19471951
Stack: stack,
1948-
Install: install,
1949-
Test: test,
1950-
SystemDeps: systemDeps,
1952+
Setup: setup,
19511953
Image: image,
19521954
ImageVersion: imageVersion,
19531955
}, nil

envbuilder/envbuilder_test.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -741,9 +741,10 @@ func TestDockerfileContent(t *testing.T) {
741741
dir := t.TempDir()
742742
writeFile(t, dir, "go.mod", "module github.com/foo/bar\n\ngo 1.22\n")
743743
env := &Environment{
744-
Stack: stackGo,
745-
Install: "go mod download",
746-
Test: "go test ./...",
744+
Stack: stackGo,
745+
Setup: []Step{
746+
{Name: "install", Command: "go mod download"},
747+
},
747748
Image: "cimg/go",
748749
ImageVersion: "1.22.3",
749750
}
@@ -762,9 +763,10 @@ func TestDockerfileContent(t *testing.T) {
762763
t.Run("non-cimg image uses plain COPY", func(t *testing.T) {
763764
t.Parallel()
764765
env := &Environment{
765-
Stack: stackPython,
766-
Install: "pip install -r requirements.txt",
767-
Test: "pytest",
766+
Stack: stackPython,
767+
Setup: []Step{
768+
{Name: "install", Command: "pip install -r requirements.txt"},
769+
},
768770
Image: "python",
769771
ImageVersion: "3.12.0",
770772
}
@@ -777,10 +779,11 @@ func TestDockerfileContent(t *testing.T) {
777779
t.Run("system dep injects RUN", func(t *testing.T) {
778780
t.Parallel()
779781
env := &Environment{
780-
Stack: stackPython,
781-
Install: "pip install uv",
782-
Test: "uv run pytest",
783-
SystemDeps: []string{"uv"},
782+
Stack: stackPython,
783+
Setup: []Step{
784+
{Name: "system", Command: "pip install uv"},
785+
{Name: "install", Command: "uv sync"},
786+
},
784787
Image: "cimg/python",
785788
ImageVersion: "3.12.0",
786789
}
@@ -797,9 +800,10 @@ members = ["crates/mypkg"]
797800
`)
798801
writeFile(t, dir, "crates/mypkg/Cargo.toml", `[package]\nname="mypkg"\n`)
799802
env := &Environment{
800-
Stack: stackPython,
801-
Install: "uv sync",
802-
Test: "uv run pytest",
803+
Stack: stackPython,
804+
Setup: []Step{
805+
{Name: "install", Command: "uv sync"},
806+
},
803807
Image: "cimg/python",
804808
ImageVersion: "3.12.0",
805809
}
@@ -811,10 +815,11 @@ members = ["crates/mypkg"]
811815
t.Run("sudo apt-get for cimg system deps", func(t *testing.T) {
812816
t.Parallel()
813817
env := &Environment{
814-
Stack: stackGo,
815-
Install: "go mod download",
816-
Test: "go test ./...",
817-
SystemDeps: []string{"git"},
818+
Stack: stackGo,
819+
Setup: []Step{
820+
{Name: "system", Command: "sudo apt-get update && sudo apt-get install -y git --no-install-recommends && sudo rm -rf /var/lib/apt/lists/*"},
821+
{Name: "install", Command: "go mod download"},
822+
},
818823
Image: "cimg/go",
819824
ImageVersion: "1.22.0",
820825
}

0 commit comments

Comments
 (0)