|
7 | 7 | "maps" |
8 | 8 | "os" |
9 | 9 | "os/exec" |
| 10 | + "runtime" |
10 | 11 | "slices" |
| 12 | + "strings" |
11 | 13 |
|
12 | 14 | "github.com/spf13/cobra" |
13 | 15 | flag "github.com/spf13/pflag" |
@@ -88,41 +90,56 @@ var ( |
88 | 90 | // Ensure deterministic umask for builds. |
89 | 91 | setUmask(0o002) |
90 | 92 |
|
| 93 | + // Determine builder image to use. |
| 94 | + builderImage := "" |
| 95 | + if manifest.Artifacts != nil { |
| 96 | + builderImage = strings.TrimSpace(manifest.Artifacts.Builder) |
| 97 | + if manifest.Artifacts.Builder != "" && builderImage == "" { |
| 98 | + return fmt.Errorf("builder image is empty after trimming whitespace") |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + // Determine if we need to use a container for building. |
| 103 | + // Native builds are only supported on Linux. On other platforms, we require |
| 104 | + // a container. |
| 105 | + nativeBuildSupported := runtime.GOOS == "linux" && runtime.GOARCH == "amd64" |
| 106 | + containerAvailable := env.IsContainerRuntimeAvailable() |
| 107 | + |
91 | 108 | var buildEnv env.ExecEnv |
92 | 109 | switch { |
93 | | - case manifest.Artifacts == nil || manifest.Artifacts.Builder == "" || noContainer: |
| 110 | + case noContainer: |
| 111 | + if !nativeBuildSupported { |
| 112 | + return fmt.Errorf("native ROFL builds are only supported on linux/amd64; remove --no-container to use containerized builds on %s/%s", runtime.GOOS, runtime.GOARCH) |
| 113 | + } |
| 114 | + // Force native build regardless of manifest. |
94 | 115 | buildEnv = env.NewNativeEnv() |
95 | 116 | default: |
96 | | - var baseDir string |
97 | | - baseDir, err = env.GetBasedir() |
98 | | - if err != nil { |
99 | | - return fmt.Errorf("failed to determine base directory: %w", err) |
| 117 | + useContainer := false |
| 118 | + if !nativeBuildSupported { |
| 119 | + useContainer = true |
| 120 | + } else if builderImage != "" { |
| 121 | + useContainer = true |
100 | 122 | } |
101 | 123 |
|
102 | | - containerEnv := env.NewContainerEnv( |
103 | | - manifest.Artifacts.Builder, |
104 | | - baseDir, |
105 | | - "/src", |
106 | | - ) |
107 | | - containerEnv.AddDirectory(tmpDir) |
108 | | - buildEnv = containerEnv |
109 | | - |
110 | | - if buildEnv.IsAvailable() { |
111 | | - fmt.Printf("Initializing build environment...\n") |
112 | | - // Run a dummy command to make sure that all necessary Docker layers |
113 | | - // for the build environment are downloaded at the start instead of |
114 | | - // later in the build process. |
115 | | - // Also pipe all output from the process to stdout/stderr, so the user |
116 | | - // can follow the progress in real-time. |
117 | | - cmd := exec.Command("true") |
118 | | - cmd.Stdout = os.Stdout |
119 | | - cmd.Stderr = os.Stderr |
120 | | - if err = buildEnv.WrapCommand(cmd); err != nil { |
121 | | - return fmt.Errorf("unable to wrap command: %w", err) |
122 | | - } |
123 | | - if err = cmd.Run(); err != nil { |
124 | | - return fmt.Errorf("failed to initialize build environment: %w", err) |
| 124 | + if !useContainer { |
| 125 | + buildEnv = env.NewNativeEnv() |
| 126 | + break |
| 127 | + } |
| 128 | + |
| 129 | + if !containerAvailable { |
| 130 | + if builderImage != "" { |
| 131 | + return fmt.Errorf("builder specified in manifest but no container runtime (docker or podman) is available") |
125 | 132 | } |
| 133 | + return fmt.Errorf("native ROFL builds are only supported on linux/amd64; on %s/%s you need docker or podman installed", runtime.GOOS, runtime.GOARCH) |
| 134 | + } |
| 135 | + |
| 136 | + if builderImage == "" { |
| 137 | + builderImage = buildRofl.DefaultBuilderImage |
| 138 | + } |
| 139 | + fmt.Printf("Using container build environment (image: %s)\n", builderImage) |
| 140 | + buildEnv, err = setupContainerEnv(builderImage, tmpDir) |
| 141 | + if err != nil { |
| 142 | + return err |
126 | 143 | } |
127 | 144 | } |
128 | 145 |
|
@@ -304,6 +321,39 @@ var ( |
304 | 321 | } |
305 | 322 | ) |
306 | 323 |
|
| 324 | +// setupContainerEnv creates and initializes a container build environment. |
| 325 | +func setupContainerEnv(builderImage, tmpDir string) (env.ExecEnv, error) { |
| 326 | + baseDir, err := env.GetBasedir() |
| 327 | + if err != nil { |
| 328 | + return nil, fmt.Errorf("failed to determine base directory: %w", err) |
| 329 | + } |
| 330 | + |
| 331 | + containerEnv := env.NewContainerEnv( |
| 332 | + builderImage, |
| 333 | + baseDir, |
| 334 | + "/src", |
| 335 | + ) |
| 336 | + containerEnv.AddDirectory(tmpDir) |
| 337 | + |
| 338 | + fmt.Printf("Initializing build environment...\n") |
| 339 | + // Run a dummy command to make sure that all necessary Docker layers |
| 340 | + // for the build environment are downloaded at the start instead of |
| 341 | + // later in the build process. |
| 342 | + // Also pipe all output from the process to stdout/stderr, so the user |
| 343 | + // can follow the progress in real-time. |
| 344 | + cmd := exec.Command("true") |
| 345 | + cmd.Stdout = os.Stdout |
| 346 | + cmd.Stderr = os.Stderr |
| 347 | + if err = containerEnv.WrapCommand(cmd); err != nil { |
| 348 | + return nil, fmt.Errorf("unable to wrap command: %w", err) |
| 349 | + } |
| 350 | + if err = cmd.Run(); err != nil { |
| 351 | + return nil, fmt.Errorf("failed to initialize build environment with image %s (ensure the image is accessible and your container runtime can pull it): %w", builderImage, err) |
| 352 | + } |
| 353 | + |
| 354 | + return containerEnv, nil |
| 355 | +} |
| 356 | + |
307 | 357 | func setupBuildEnv(deployment *buildRofl.Deployment, npa *common.NPASelection) { |
308 | 358 | // Configure app ID. |
309 | 359 | os.Setenv("ROFL_APP_ID", deployment.AppID) |
|
0 commit comments