Skip to content

Commit 3ca8ec6

Browse files
authored
Make macOS dpd mgd build from source workaround official (#9312)
Dendrite and maghemite do not build mac binaries for `dpd`, `swadm`, and `mgd`. To work around this, I wrote a [script](https://gist.github.com/david-crespo/797f955fa116a1ebbb720ea3513e6f29) I've been passing around to all macOS users in secret. This script checks out the right commit locally and builds those binaries from source. @davepacheco suggested this could just be part of the normal install xtask. Claude got it done in about 5 minutes and it appears to work quite well. Differences from the original script to not assume anything about your computer: 1. Commit-hash based binary caching is portable — cache lives in `out/.build-cache/{project}/{commit}/` 1. Clone dendrite and maghemite from scratch in temp dirs instead of assuming they are already cloned next to omicron
1 parent c6a222a commit 3ca8ec6

File tree

5 files changed

+145
-10
lines changed

5 files changed

+145
-10
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-tools/downloader/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ slog-async.workspace = true
2121
slog-term.workspace = true
2222
strum.workspace = true
2323
tar.workspace = true
24+
tempfile.workspace = true
2425
tokio.workspace = true

dev-tools/downloader/src/lib.rs

Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,114 @@ impl<'a> Downloader<'a> {
216216
) -> Self {
217217
Self { log, output_dir, versions_dir }
218218
}
219+
220+
/// Build a binary from a git repository at a specific commit.
221+
///
222+
/// This function:
223+
/// 1. Checks for cached binaries at `out/.build-cache/{project}/{commit}/`
224+
/// 2. If not cached, shallow clones the repo to a temp directory
225+
/// 3. Builds the specified binaries with cargo
226+
/// 4. Caches the built binaries
227+
/// 5. Returns paths to the cached binaries
228+
async fn build_from_git(
229+
&self,
230+
project: &str,
231+
commit: &str,
232+
binaries: &[(&str, &[&str])], // (binary_name, cargo_args)
233+
) -> Result<Vec<Utf8PathBuf>> {
234+
let cache_dir =
235+
self.output_dir.join(".build-cache").join(project).join(commit);
236+
237+
// Check if all binaries are already cached
238+
let mut cached_paths = Vec::new();
239+
let mut all_cached = true;
240+
for (binary_name, _) in binaries {
241+
let cached_path = cache_dir.join(binary_name);
242+
if !cached_path.exists() {
243+
all_cached = false;
244+
break;
245+
}
246+
cached_paths.push(cached_path);
247+
}
248+
249+
if all_cached {
250+
info!(self.log, "Found cached binaries for {project} at {commit}"; "cache_dir" => %cache_dir);
251+
return Ok(cached_paths);
252+
}
253+
254+
// Need to build - create temp directory
255+
info!(self.log, "Building {project} from source at commit {commit}");
256+
let temp_dir = tempfile::tempdir()?;
257+
let temp_path = Utf8PathBuf::try_from(temp_dir.path().to_path_buf())?;
258+
259+
// Clone and checkout the specific commit
260+
let repo_url = format!("https://github.com/oxidecomputer/{}", project);
261+
info!(self.log, "Cloning {repo_url}");
262+
let mut clone_cmd = Command::new("git");
263+
clone_cmd
264+
.arg("clone")
265+
.arg("--filter=blob:none")
266+
.arg(&repo_url)
267+
.arg(&temp_path);
268+
269+
let clone_output = clone_cmd.output().await?;
270+
if !clone_output.status.success() {
271+
let stderr = String::from_utf8_lossy(&clone_output.stderr);
272+
bail!("Failed to clone {repo_url}: {stderr}");
273+
}
274+
275+
// Checkout the specific commit
276+
info!(self.log, "Checking out commit {commit}");
277+
let mut checkout_cmd = Command::new("git");
278+
checkout_cmd.arg("checkout").arg(commit).current_dir(&temp_path);
279+
280+
let checkout_output = checkout_cmd.output().await?;
281+
if !checkout_output.status.success() {
282+
let stderr = String::from_utf8_lossy(&checkout_output.stderr);
283+
bail!("Failed to checkout {commit}: {stderr}");
284+
}
285+
286+
// Build each binary
287+
tokio::fs::create_dir_all(&cache_dir).await?;
288+
let mut result_paths = Vec::new();
289+
290+
for (binary_name, cargo_args) in binaries {
291+
info!(self.log, "Building {binary_name}"; "args" => ?cargo_args);
292+
293+
let mut build_cmd = Command::new("cargo");
294+
build_cmd
295+
.arg("build")
296+
.arg("--release")
297+
.arg("--bin")
298+
.arg(binary_name)
299+
.args(*cargo_args)
300+
.current_dir(&temp_path);
301+
302+
let build_output = build_cmd.output().await?;
303+
if !build_output.status.success() {
304+
let stderr = String::from_utf8_lossy(&build_output.stderr);
305+
bail!("Failed to build {binary_name}: {stderr}");
306+
}
307+
308+
// Always build in release mode
309+
let source_path =
310+
temp_path.join("target").join("release").join(binary_name);
311+
312+
if !source_path.exists() {
313+
bail!("Expected binary not found at {source_path}");
314+
}
315+
316+
// Copy to cache
317+
let cached_path = cache_dir.join(binary_name);
318+
tokio::fs::copy(&source_path, &cached_path).await?;
319+
set_permissions(&cached_path, 0o755).await?;
320+
321+
result_paths.push(cached_path);
322+
}
323+
324+
info!(self.log, "Successfully built and cached {project} binaries");
325+
Ok(result_paths)
326+
}
219327
}
220328

221329
/// Parses a file of the format:
@@ -738,13 +846,24 @@ impl Downloader<'_> {
738846
}
739847
Os::Illumos => {}
740848
Os::Mac => {
741-
warn!(self.log, "WARNING: Dendrite not available for Mac");
742-
warn!(self.log, "Network APIs will be unavailable");
743-
744-
let path = bin_dir.join("dpd");
745-
tokio::fs::write(&path, "echo 'unsupported os' && exit 1")
746-
.await?;
747-
set_permissions(&path, 0o755).await?;
849+
info!(self.log, "Building dendrite from source for macOS");
850+
851+
let binaries = [
852+
("dpd", &["--features=tofino_stub"][..]),
853+
("swadm", &[][..]),
854+
];
855+
856+
let built_binaries =
857+
self.build_from_git("dendrite", &commit, &binaries).await?;
858+
859+
// Copy built binaries to bin_dir
860+
for (binary_path, (binary_name, _)) in
861+
built_binaries.iter().zip(binaries.iter())
862+
{
863+
let dest = bin_dir.join(binary_name);
864+
tokio::fs::copy(binary_path, &dest).await?;
865+
set_permissions(&dest, 0o755).await?;
866+
}
748867
}
749868
}
750869

@@ -807,7 +926,21 @@ impl Downloader<'_> {
807926
set_permissions(&path, 0o755).await?;
808927
tokio::fs::copy(path, binary_dir.join(filename)).await?;
809928
}
810-
_ => (),
929+
Os::Mac => {
930+
info!(self.log, "Building maghemite from source for macOS");
931+
932+
let binaries = [("mgd", &["--no-default-features"][..])];
933+
934+
let built_binaries = self
935+
.build_from_git("maghemite", &commit, &binaries)
936+
.await?;
937+
938+
// Copy built binary to binary_dir
939+
let dest = binary_dir.join("mgd");
940+
tokio::fs::copy(&built_binaries[0], &dest).await?;
941+
set_permissions(&dest, 0o755).await?;
942+
}
943+
Os::Illumos => (),
811944
}
812945

813946
Ok(())

tools/install_builder_prerequisites.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ function install_packages {
199199
)
200200
confirm "Install (or update) [${packages[*]}]?" && brew install "${packages[@]}"
201201
else
202-
echo "Unsupported OS: ${HOST_OS}"
202+
echo "Skipping builder prereqs for unsupported OS: ${HOST_OS}"
203203
exit 1
204204
fi
205205
}

tools/install_runner_prerequisites.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function install_packages {
153153
confirm "Install (or update) [${packages[*]}]?" && $maybe_sudo apt-get install "${packages[@]}"
154154
fi
155155
else
156-
echo "Unsupported OS: ${HOST_OS}"
156+
echo "Skipping runner prereqs for unsupported OS: ${HOST_OS}"
157157
exit 1
158158
fi
159159
}

0 commit comments

Comments
 (0)