@@ -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
6548func 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 ("\n RUN " + cmd + "\n " )
464- }
437+ if sysCmd := env .SetupStep ("system" ); sysCmd != "" {
438+ sb .WriteString ("\n RUN " + 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 ("\n COPY " + 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 ("\n COPY " + 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 ("\n ENV 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 ("\n RUN " + env .Install + "\n " )
608+ if installCmd := env .SetupStep ("install" ); installCmd != "" {
609+ sb .WriteString ("\n RUN " + 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 ("\n CMD 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 ("\n CMD " + env . Test + "\n " )
695+ } else if testCmd := env . SetupStep ( "test" ); testCmd != "" {
696+ sb .WriteString ("\n CMD " + 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
0 commit comments