Skip to content

Commit e877ee8

Browse files
committed
feat(cmd/rofl): Add support for Dockerized builders
1 parent c1cb32a commit e877ee8

File tree

10 files changed

+342
-31
lines changed

10 files changed

+342
-31
lines changed

build/cargo/cargo.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"os/exec"
99
"slices"
1010
"strings"
11+
12+
"github.com/oasisprotocol/cli/build/env"
1113
)
1214

1315
// Metadata is the cargo package metadata.
@@ -42,12 +44,15 @@ func (d *Dependency) HasFeature(feature string) bool {
4244
}
4345

4446
// GetMetadata queries `cargo` for metadata of the package in the current working directory.
45-
func GetMetadata() (*Metadata, error) {
47+
func GetMetadata(env env.ExecEnv) (*Metadata, error) {
4648
cmd := exec.Command("cargo", "metadata", "--no-deps", "--format-version", "1")
4749
stdout, err := cmd.StdoutPipe()
4850
if err != nil {
4951
return nil, fmt.Errorf("failed to initialize metadata process: %w", err)
5052
}
53+
if err = env.WrapCommand(cmd); err != nil {
54+
return nil, err
55+
}
5156
if err = cmd.Start(); err != nil {
5257
return nil, fmt.Errorf("failed to start metadata process: %w", err)
5358
}
@@ -105,8 +110,8 @@ func GetMetadata() (*Metadata, error) {
105110
}
106111

107112
// Build builds a Rust program using `cargo` in the current working directory.
108-
func Build(release bool, target string, features []string) (string, error) {
109-
args := []string{"build"}
113+
func Build(env env.ExecEnv, release bool, target string, features []string) (string, error) {
114+
args := []string{"build", "--locked"}
110115
if release {
111116
args = append(args, "--release")
112117
}
@@ -128,6 +133,9 @@ func Build(release bool, target string, features []string) (string, error) {
128133
var stderr bytes.Buffer
129134
cmd.Stderr = &stderr
130135

136+
if err = env.WrapCommand(cmd); err != nil {
137+
return "", err
138+
}
131139
if err = cmd.Start(); err != nil {
132140
return "", fmt.Errorf("failed to start build process: %w", err)
133141
}
@@ -171,5 +179,9 @@ func Build(release bool, target string, features []string) (string, error) {
171179
if executable == "" {
172180
return "", fmt.Errorf("no executable generated")
173181
}
182+
executable, err = env.PathFromEnv(executable)
183+
if err != nil {
184+
return "", fmt.Errorf("failed to map executable path: %w", err)
185+
}
174186
return executable, nil
175187
}

build/env/base.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package env
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
// GetBasedir finds the Git repository root directory and use that as base. If one cannot be found,
9+
// uses the current working directory.
10+
func GetBasedir() (string, error) {
11+
wd, err := os.Getwd()
12+
if err != nil {
13+
return "", err
14+
}
15+
16+
dir := getGitRoot(wd)
17+
if dir != "" {
18+
return dir, nil
19+
}
20+
return wd, nil
21+
}
22+
23+
func getGitRoot(dir string) string {
24+
currentDir := dir
25+
26+
for {
27+
entries, err := os.ReadDir(currentDir)
28+
if err != nil {
29+
return ""
30+
}
31+
32+
for _, de := range entries {
33+
if !de.IsDir() {
34+
continue
35+
}
36+
if de.Name() == ".git" {
37+
return currentDir
38+
}
39+
}
40+
41+
parentDir := filepath.Dir(currentDir)
42+
if parentDir == currentDir {
43+
return ""
44+
}
45+
currentDir = parentDir
46+
}
47+
}

build/env/env.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
// ExecEnv is an execution environment.
12+
type ExecEnv interface {
13+
// WrapCommand modifies an existing `exec.Cmd` such that it runs in this environment.
14+
WrapCommand(cmd *exec.Cmd) error
15+
16+
// PathFromEnv converts the given path from inside the environment into a path outside the
17+
// environment.
18+
PathFromEnv(path string) (string, error)
19+
20+
// PathToEnv converts the given path from outside the environment into a path inside the
21+
// environment.
22+
PathToEnv(path string) (string, error)
23+
24+
// FixPermissions ensures that the user executing this process owns the file at the given path
25+
// outside the environment.
26+
FixPermissions(path string) error
27+
28+
// HasBinary returns true iff the given binary name is available in this environment.
29+
HasBinary(name string) bool
30+
}
31+
32+
// PlainEnv is a plain execution environment that executes all commands directly.
33+
type PlainEnv struct{}
34+
35+
// NewPlainEnv creates a new plain execution environment.
36+
func NewPlainEnv() *PlainEnv {
37+
return &PlainEnv{}
38+
}
39+
40+
// WrapCommand implements ExecEnv.
41+
func (pe *PlainEnv) WrapCommand(*exec.Cmd) error {
42+
return nil
43+
}
44+
45+
// PathFromEnv implements ExecEnv.
46+
func (pe *PlainEnv) PathFromEnv(path string) (string, error) {
47+
return path, nil
48+
}
49+
50+
// PathToEnv implements ExecEnv.
51+
func (pe *PlainEnv) PathToEnv(path string) (string, error) {
52+
return path, nil
53+
}
54+
55+
// FixPermissions implements ExecEnv.
56+
func (pe *PlainEnv) FixPermissions(string) error {
57+
return nil
58+
}
59+
60+
// HasBinary implements ExecEnv.
61+
func (pe *PlainEnv) HasBinary(name string) bool {
62+
path, err := exec.LookPath(name)
63+
return err == nil && path != ""
64+
}
65+
66+
// DockerEnv is a Docker-based execution environment that executes all commands inside a Docker
67+
// container using the configured image.
68+
type DockerEnv struct {
69+
image string
70+
volumes map[string]string
71+
}
72+
73+
// NewDockerEnv creates a new Docker-based execution environment.
74+
func NewDockerEnv(image, baseDir, dirMount string) *DockerEnv {
75+
return &DockerEnv{
76+
image: image,
77+
volumes: map[string]string{
78+
baseDir: dirMount,
79+
},
80+
}
81+
}
82+
83+
// AddDirectory exposes a host directory to the container under the same path.
84+
func (de *DockerEnv) AddDirectory(path string) {
85+
de.volumes[path] = path
86+
}
87+
88+
// WrapCommand implements ExecEnv.
89+
func (de *DockerEnv) WrapCommand(cmd *exec.Cmd) error {
90+
origArgs := cmd.Args
91+
92+
var err error
93+
wd := cmd.Dir
94+
if wd == "" {
95+
wd, err = os.Getwd()
96+
if err != nil {
97+
return err
98+
}
99+
}
100+
workDir, err := de.PathToEnv(wd)
101+
if err != nil {
102+
return fmt.Errorf("bad working directory: %w", err)
103+
}
104+
105+
var envArgs []string //nolint: prealloc
106+
for _, envKV := range cmd.Env {
107+
envArgs = append(envArgs, "--env", envKV)
108+
}
109+
// When no environment is set, copy over any OASIS_ and ROFL_ variables.
110+
if len(cmd.Env) == 0 {
111+
for _, envKV := range os.Environ() {
112+
if !strings.HasPrefix(envKV, "OASIS_") && !strings.HasPrefix(envKV, "ROFL_") {
113+
continue
114+
}
115+
envArgs = append(envArgs, "--env", envKV)
116+
}
117+
}
118+
119+
cmd.Path, err = exec.LookPath("docker")
120+
if err != nil {
121+
return fmt.Errorf("failed to find 'docker': %w", err)
122+
}
123+
124+
cmd.Args = []string{
125+
"docker", "run",
126+
"--rm",
127+
"--platform", "linux/amd64",
128+
"--workdir", workDir,
129+
}
130+
for hostDir, bindDir := range de.volumes {
131+
cmd.Args = append(cmd.Args, "--volume", hostDir+":"+bindDir)
132+
}
133+
cmd.Args = append(cmd.Args, envArgs...)
134+
cmd.Args = append(cmd.Args, de.image)
135+
cmd.Args = append(cmd.Args, origArgs...)
136+
137+
return nil
138+
}
139+
140+
// PathFromEnv implements ExecEnv.
141+
func (de *DockerEnv) PathFromEnv(path string) (string, error) {
142+
for hostDir, bindDir := range de.volumes {
143+
if !strings.HasPrefix(path, bindDir) {
144+
continue
145+
}
146+
relPath, err := filepath.Rel(bindDir, path)
147+
if err != nil {
148+
return "", fmt.Errorf("bad path: %w", err)
149+
}
150+
return filepath.Join(hostDir, relPath), nil
151+
}
152+
return "", fmt.Errorf("bad path '%s'", path)
153+
}
154+
155+
// PathToEnv implements ExecEnv.
156+
func (de *DockerEnv) PathToEnv(path string) (string, error) {
157+
for hostDir, bindDir := range de.volumes {
158+
if !strings.HasPrefix(path, hostDir) {
159+
continue
160+
}
161+
relPath, err := filepath.Rel(hostDir, path)
162+
if err != nil {
163+
return "", fmt.Errorf("bad path: %w", err)
164+
}
165+
return filepath.Join(bindDir, relPath), nil
166+
}
167+
return "", fmt.Errorf("bad path '%s'", path)
168+
}
169+
170+
// FixPermissions implements ExecEnv.
171+
func (de *DockerEnv) FixPermissions(path string) error {
172+
path, err := de.PathToEnv(path)
173+
if err != nil {
174+
return err
175+
}
176+
177+
cmd := exec.Command("chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), path) //nolint: gosec
178+
if err = de.WrapCommand(cmd); err != nil {
179+
return err
180+
}
181+
return cmd.Run()
182+
}
183+
184+
// HasBinary implements ExecEnv.
185+
func (de *DockerEnv) HasBinary(string) bool {
186+
return true
187+
}

build/rofl/manifest.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,11 +395,13 @@ func (e *StorageConfig) Validate() error {
395395

396396
// ArtifactsConfig is the artifact location override configuration.
397397
type ArtifactsConfig struct {
398-
// Firmware is the URI/path to the firmware artifact (empty to use default).
398+
// Builder is the OCI reference to the builder container image. Empty to not use a builder.
399+
Builder string `yaml:"builder,omitempty" json:"builder,omitempty"`
400+
// Firmware is the URI/path to the firmware artifact.
399401
Firmware string `yaml:"firmware,omitempty" json:"firmware,omitempty"`
400-
// Kernel is the URI/path to the kernel artifact (empty to use default).
402+
// Kernel is the URI/path to the kernel artifact.
401403
Kernel string `yaml:"kernel,omitempty" json:"kernel,omitempty"`
402-
// Stage2 is the URI/path to the stage 2 disk artifact (empty to use default).
404+
// Stage2 is the URI/path to the stage 2 disk artifact.
403405
Stage2 string `yaml:"stage2,omitempty" json:"stage2,omitempty"`
404406
// Container is the container artifacts configuration.
405407
Container ContainerArtifactsConfig `yaml:"container,omitempty" json:"container,omitempty"`

build/sgxs/sgxs.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ package sgxs
44
import (
55
"os/exec"
66
"strconv"
7+
8+
"github.com/oasisprotocol/cli/build/env"
79
)
810

911
// Elf2Sgxs converts an ELF binary built for the SGX ABI into an SGXS binary.
1012
//
1113
// It requires the `ftxsgx-elf2sgxs` utility to be installed.
12-
func Elf2Sgxs(elfSgxPath, sgxsPath string, heapSize, stackSize, threads uint64) error {
14+
func Elf2Sgxs(buildEnv env.ExecEnv, elfSgxPath, sgxsPath string, heapSize, stackSize, threads uint64) error {
1315
args := []string{
1416
elfSgxPath,
1517
"--heap-size", strconv.FormatUint(heapSize, 10),
@@ -19,5 +21,8 @@ func Elf2Sgxs(elfSgxPath, sgxsPath string, heapSize, stackSize, threads uint64)
1921
}
2022

2123
cmd := exec.Command("ftxsgx-elf2sgxs", args...)
24+
if err := buildEnv.WrapCommand(cmd); err != nil {
25+
return err
26+
}
2227
return cmd.Run()
2328
}

0 commit comments

Comments
 (0)