Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions build/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"

"github.com/oasisprotocol/cli/cmd/common"
)
Expand Down Expand Up @@ -86,6 +87,10 @@ type ContainerEnv struct {
}

var containerCmds = []string{"docker", "podman"}
var (
containerCmdPath string
containerCmdOnce sync.Once
)

// NewContainerEnv creates a new Docker or Podman-based execution environment.
func NewContainerEnv(image, baseDir, dirMount string) *ContainerEnv {
Expand Down Expand Up @@ -213,12 +218,20 @@ func (de *ContainerEnv) HasBinary(string) bool {

// getContainerCmd finds a working docker or podman command and returns its path.
func getContainerCmd() string {
for _, cmd := range containerCmds {
if path, err := exec.LookPath(cmd); err == nil && path != "" {
return path
containerCmdOnce.Do(func() {
for _, cmd := range containerCmds {
if path, err := exec.LookPath(cmd); err == nil && path != "" {
containerCmdPath = path
return
}
}
}
return ""
})
return containerCmdPath
}

// IsContainerRuntimeAvailable returns true if a container runtime (docker or podman) is available.
func IsContainerRuntimeAvailable() bool {
return getContainerCmd() != ""
}

// IsAvailable implements ExecEnv.
Expand Down
3 changes: 3 additions & 0 deletions build/rofl/artifacts.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package rofl

// DefaultBuilderImage is the default builder container image to use when building ROFL apps.
const DefaultBuilderImage = "ghcr.io/oasisprotocol/rofl-dev:v0.5.0@sha256:31573686552abeb0edebc450f6872831f0006a6cf38220cef7e0789d4376c2c1"

// LatestBasicArtifacts are the latest TDX ROFL basic app artifacts.
var LatestBasicArtifacts = ArtifactsConfig{
Firmware: "https://github.com/oasisprotocol/oasis-boot/releases/download/v0.6.2/ovmf.tdx.fd#db47100a7d6a0c1f6983be224137c3f8d7cb09b63bb1c7a5ee7829d8e994a42f",
Expand Down
6 changes: 3 additions & 3 deletions build/sgxs/sgxs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
// It requires the `ftxsgx-elf2sgxs` utility to be installed.
func Elf2Sgxs(buildEnv env.ExecEnv, elfSgxPath, sgxsPath string, heapSize, stackSize, threads uint64) (err error) {
if elfSgxPath, err = buildEnv.PathToEnv(elfSgxPath); err != nil {
return
return err
}
if sgxsPath, err = buildEnv.PathToEnv(sgxsPath); err != nil {
return
return err
}

args := []string{
Expand All @@ -31,7 +31,7 @@ func Elf2Sgxs(buildEnv env.ExecEnv, elfSgxPath, sgxsPath string, heapSize, stack

cmd := exec.Command("ftxsgx-elf2sgxs", args...)
if err = buildEnv.WrapCommand(cmd); err != nil {
return
return err
}
if common.IsVerbose() {
fmt.Println(cmd)
Expand Down
106 changes: 78 additions & 28 deletions cmd/rofl/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"maps"
"os"
"os/exec"
"runtime"
"slices"
"strings"

"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
Expand Down Expand Up @@ -88,41 +90,56 @@ var (
// Ensure deterministic umask for builds.
setUmask(0o002)

// Determine builder image to use.
builderImage := ""
if manifest.Artifacts != nil {
builderImage = strings.TrimSpace(manifest.Artifacts.Builder)
if manifest.Artifacts.Builder != "" && builderImage == "" {
return fmt.Errorf("builder image is empty after trimming whitespace")
}
}

// Determine if we need to use a container for building.
// Native builds are only supported on Linux. On other platforms, we require
// a container.
nativeBuildSupported := runtime.GOOS == "linux" && runtime.GOARCH == "amd64"
containerAvailable := env.IsContainerRuntimeAvailable()

var buildEnv env.ExecEnv
switch {
case manifest.Artifacts == nil || manifest.Artifacts.Builder == "" || noContainer:
case noContainer:
if !nativeBuildSupported {
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)
}
// Force native build regardless of manifest.
buildEnv = env.NewNativeEnv()
default:
var baseDir string
baseDir, err = env.GetBasedir()
if err != nil {
return fmt.Errorf("failed to determine base directory: %w", err)
useContainer := false
if !nativeBuildSupported {
useContainer = true
} else if builderImage != "" {
useContainer = true
}

containerEnv := env.NewContainerEnv(
manifest.Artifacts.Builder,
baseDir,
"/src",
)
containerEnv.AddDirectory(tmpDir)
buildEnv = containerEnv

if buildEnv.IsAvailable() {
fmt.Printf("Initializing build environment...\n")
// Run a dummy command to make sure that all necessary Docker layers
// for the build environment are downloaded at the start instead of
// later in the build process.
// Also pipe all output from the process to stdout/stderr, so the user
// can follow the progress in real-time.
cmd := exec.Command("true")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = buildEnv.WrapCommand(cmd); err != nil {
return fmt.Errorf("unable to wrap command: %w", err)
}
if err = cmd.Run(); err != nil {
return fmt.Errorf("failed to initialize build environment: %w", err)
if !useContainer {
buildEnv = env.NewNativeEnv()
break
}

if !containerAvailable {
if builderImage != "" {
return fmt.Errorf("builder specified in manifest but no container runtime (docker or podman) is available")
}
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)
}

if builderImage == "" {
builderImage = buildRofl.DefaultBuilderImage
}
fmt.Printf("Using container build environment (image: %s)\n", builderImage)
buildEnv, err = setupContainerEnv(builderImage, tmpDir)
if err != nil {
return err
}
}

Expand Down Expand Up @@ -304,6 +321,39 @@ var (
}
)

// setupContainerEnv creates and initializes a container build environment.
func setupContainerEnv(builderImage, tmpDir string) (env.ExecEnv, error) {
baseDir, err := env.GetBasedir()
if err != nil {
return nil, fmt.Errorf("failed to determine base directory: %w", err)
}

containerEnv := env.NewContainerEnv(
builderImage,
baseDir,
"/src",
)
containerEnv.AddDirectory(tmpDir)

fmt.Printf("Initializing build environment...\n")
// Run a dummy command to make sure that all necessary Docker layers
// for the build environment are downloaded at the start instead of
// later in the build process.
// Also pipe all output from the process to stdout/stderr, so the user
// can follow the progress in real-time.
cmd := exec.Command("true")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = containerEnv.WrapCommand(cmd); err != nil {
return nil, fmt.Errorf("unable to wrap command: %w", err)
}
if err = cmd.Run(); err != nil {
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)
}

return containerEnv, nil
}

func setupBuildEnv(deployment *buildRofl.Deployment, npa *common.NPASelection) {
// Configure app ID.
os.Setenv("ROFL_APP_ID", deployment.AppID)
Expand Down
10 changes: 10 additions & 0 deletions docs/rofl.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ Building ROFL apps does not require a working TEE on your machine. However, you
do need to install all corresponding tools. Check out the [ROFL Prerequisites]
chapter for details.

Platform/container selection rules:

- On Linux/amd64, builds are native by default unless the manifest specifies a
`builder` image; you can force native builds with `--no-container` even if a
`builder` is set.
- On macOS/Windows/other architectures, builds always use a containerized
builder; `--no-container` will fail on these platforms.
- If a containerized build is selected (due to platform or manifest), Docker or
Podman must be available; otherwise the build fails.

:::

[ROFL Prerequisites]: https://github.com/oasisprotocol/oasis-sdk/blob/main/docs/rofl/workflow/prerequisites.md
Expand Down
Loading