|
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,41 @@ 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 | + // Native builds are only supported on Linux. |
| 102 | + nativeBuildSupported := runtime.GOOS == "linux" && runtime.GOARCH == "amd64" |
| 103 | + |
91 | 104 | var buildEnv env.ExecEnv |
92 | 105 | switch { |
93 | | - case manifest.Artifacts == nil || manifest.Artifacts.Builder == "" || noContainer: |
| 106 | + case noContainer: |
| 107 | + // Force native build regardless of manifest. |
| 108 | + if !nativeBuildSupported { |
| 109 | + 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) |
| 110 | + } |
94 | 111 | buildEnv = env.NewNativeEnv() |
| 112 | + case builderImage == "": |
| 113 | + // No builder image specified. |
| 114 | + if nativeBuildSupported { |
| 115 | + buildEnv = env.NewNativeEnv() |
| 116 | + } else { |
| 117 | + return fmt.Errorf("no builder image specified in manifest; run `oasis rofl upgrade` to add the default builder or set artifacts.builder") |
| 118 | + } |
95 | 119 | 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) |
| 120 | + // Builder image specified. |
| 121 | + if !env.IsContainerRuntimeAvailable() { |
| 122 | + return fmt.Errorf("builder specified in manifest but no container runtime (docker or podman) is available") |
100 | 123 | } |
101 | | - |
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) |
125 | | - } |
| 124 | + fmt.Printf("Using container build environment (image: %s)\n", builderImage) |
| 125 | + buildEnv, err = setupContainerEnv(builderImage, tmpDir) |
| 126 | + if err != nil { |
| 127 | + return err |
126 | 128 | } |
127 | 129 | } |
128 | 130 |
|
@@ -304,6 +306,39 @@ var ( |
304 | 306 | } |
305 | 307 | ) |
306 | 308 |
|
| 309 | +// setupContainerEnv creates and initializes a container build environment. |
| 310 | +func setupContainerEnv(builderImage, tmpDir string) (env.ExecEnv, error) { |
| 311 | + baseDir, err := env.GetBasedir() |
| 312 | + if err != nil { |
| 313 | + return nil, fmt.Errorf("failed to determine base directory: %w", err) |
| 314 | + } |
| 315 | + |
| 316 | + containerEnv := env.NewContainerEnv( |
| 317 | + builderImage, |
| 318 | + baseDir, |
| 319 | + "/src", |
| 320 | + ) |
| 321 | + containerEnv.AddDirectory(tmpDir) |
| 322 | + |
| 323 | + fmt.Printf("Initializing build environment...\n") |
| 324 | + // Run a dummy command to make sure that all necessary Docker layers |
| 325 | + // for the build environment are downloaded at the start instead of |
| 326 | + // later in the build process. |
| 327 | + // Also pipe all output from the process to stdout/stderr, so the user |
| 328 | + // can follow the progress in real-time. |
| 329 | + cmd := exec.Command("true") |
| 330 | + cmd.Stdout = os.Stdout |
| 331 | + cmd.Stderr = os.Stderr |
| 332 | + if err = containerEnv.WrapCommand(cmd); err != nil { |
| 333 | + return nil, fmt.Errorf("unable to wrap command: %w", err) |
| 334 | + } |
| 335 | + if err = cmd.Run(); err != nil { |
| 336 | + 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) |
| 337 | + } |
| 338 | + |
| 339 | + return containerEnv, nil |
| 340 | +} |
| 341 | + |
307 | 342 | func setupBuildEnv(deployment *buildRofl.Deployment, npa *common.NPASelection) { |
308 | 343 | // Configure app ID. |
309 | 344 | os.Setenv("ROFL_APP_ID", deployment.AppID) |
|
0 commit comments