Skip to content

Commit

Permalink
start of bake implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Emilgardis committed Jun 24, 2022
1 parent 76302bb commit c5b9ea0
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 23 deletions.
37 changes: 37 additions & 0 deletions xtask/src/bake.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"group": {
"default": {
"targets": ["x86_64-unknown-linux-gnu", "x86_64-unknown-netbsd"]
}
},
"target": {
"x86_64-unknown-linux-gnu": {
"args": {},
"cache-from": [
"type=registry,ref=ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main"
],
"context": null,
"dockerfile": "/home/emil/workspace/cross/docker/Dockerfile.x86_64-unknown-linux-gnu",
"labels": {
"org.cross-rs.for-cross-target": "x86_64-unknown-linux-gnu"
},
"no-cache": false,
"platforms": ["linux/amd64"],
"tags": ["ghcr.io/cross-rs/x86_64-unknown-linux-gnu:local"]
},
"x86_64-unknown-netbsd": {
"args": {},
"cache-from": [
"type=registry,ref=ghcr.io/cross-rs/x86_64-unknown-netbsd:main"
],
"context": null,
"dockerfile": "/home/emil/workspace/cross/docker/Dockerfile.x86_64-unknown-netbsd",
"labels": {
"org.cross-rs.for-cross-target": "x86_64-unknown-netbsd"
},
"no-cache": false,
"platforms": ["linux/amd64"],
"tags": ["ghcr.io/cross-rs/x86_64-unknown-netbsd:local"]
}
}
}
229 changes: 229 additions & 0 deletions xtask/src/bake_docker_image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use std::{collections::BTreeMap, path::Path};

use clap::Args;
use cross::docker;
use serde::Serialize;

use crate::build_docker_image::{get_tags, locate_dockerfile};

#[derive(Args, Debug)]
pub struct BakeDockerImage {
#[clap(long, hide = true, env = "GITHUB_REF_TYPE")]
ref_type: Option<String>,
#[clap(long, hide = true, env = "GITHUB_REF_NAME")]
ref_name: Option<String>,
#[clap(long, hide = true, env = "IS_LATEST")]
is_latest: Option<bool>,
/// Specify a tag to use instead of the derived one, eg `local`
#[clap(long)]
tag: Option<String>,
/// Repository name for image.
#[clap(long, default_value = docker::CROSS_IMAGE)]
repository: String,
/// Newline separated labels
#[clap(long, env = "LABELS")]
labels: Option<String>,
/// Provide verbose diagnostic output.
#[clap(short, long)]
pub verbose: bool,
/// Print but do not execute the build commands.
#[clap(long)]
dry_run: bool,
/// Force a push when `--push` is set, but not `--tag`
#[clap(long, hide = true)]
force: bool,
/// Push build to registry.
#[clap(short, long)]
push: bool,
/// Set output to /dev/null
#[clap(short, long)]
no_output: bool,
/// Docker build progress output type.
#[clap(
long,
value_parser = clap::builder::PossibleValuesParser::new(["auto", "plain", "tty"]),
default_value = "auto"
)]
progress: String,
/// Use a bake build
#[clap(long)]
bake: bool,
/// Do not load from cache when building the image.
#[clap(long)]
no_cache: bool,
/// Continue building images even if an image fails to build.
#[clap(long)]
no_fastfail: bool,
/// Container engine (such as docker or podman).
#[clap(long)]
pub engine: Option<String>,
/// If no target list is provided, parse list from CI.
#[clap(long)]
from_ci: bool,
/// Additional build arguments to pass to Docker.
#[clap(long)]
build_arg: Vec<String>,
/// Targets to build for
#[clap()]
targets: Vec<crate::ImageTarget>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct BakeTarget {
context: Option<String>,
dockerfile: String,
tags: Vec<String>,
labels: BTreeMap<String, String>,
platforms: Vec<String>,
args: BTreeMap<String, String>,
no_cache: bool,
cache_from: Vec<String>,
}

pub fn bake_docker_image(
BakeDockerImage {
ref_type,
ref_name,
is_latest,
tag: tag_override,
repository,
labels,
verbose,
push,
no_output,
no_cache,
from_ci,
build_arg,
mut targets,
..
}: BakeDockerImage,
engine: &docker::Engine,
) -> cross::Result<()> {
let metadata = cross::cargo_metadata_with_args(
Some(Path::new(env!("CARGO_MANIFEST_DIR"))),
None,
verbose,
)?
.ok_or_else(|| eyre::eyre!("could not find cross workspace and its current version"))?;
let version = metadata
.get_package("cross")
.expect("cross expected in workspace")
.version
.clone();
if targets.is_empty() {
if from_ci {
targets = crate::util::get_matrix()
.iter()
.filter(|m| m.os.starts_with("ubuntu"))
.map(|m| m.to_image_target())
.collect();
} else {
targets = walkdir::WalkDir::new(metadata.workspace_root.join("docker"))
.max_depth(1)
.contents_first(true)
.into_iter()
.filter_map(|e| e.ok().filter(|f| f.file_type().is_file()))
.filter_map(|f| {
f.file_name()
.to_string_lossy()
.strip_prefix("Dockerfile.")
.map(ToOwned::to_owned)
.map(|s| s.parse().unwrap())
})
.collect();
}
}
let docker_root = metadata.workspace_root.join("docker");
let cross_toolchains_root = docker_root.join("cross-toolchains").join("docker");
let targets = targets
.into_iter()
.map(|t| locate_dockerfile(t, &docker_root, &cross_toolchains_root))
.collect::<cross::Result<Vec<_>>>()?;
let build_args = build_arg
.into_iter()
.map(|s| -> cross::Result<_> {
let s = s
.split_once('=')
.ok_or_else(|| eyre::eyre!("invalid build arg `{s}`"))?;
Ok((s.0.to_string(), s.1.to_string()))
})
.collect::<cross::Result<BTreeMap<_, _>>>()?;
let labels = labels
.into_iter()
.map(|s| -> cross::Result<_> {
let s = s
.split_once('=')
.ok_or_else(|| eyre::eyre!("invalid label `{s}`"))?;
Ok((s.0.to_string(), s.1.to_string()))
})
.collect::<cross::Result<BTreeMap<_, _>>>()?;
let targets = targets
.into_iter()
.map(|(target, dockerfile)| -> cross::Result<_> {
Ok((
target.to_string().replace('.', "-"),
BakeTarget {
context: None,
dockerfile,
tags: get_tags(
&target,
&repository,
&version,
is_latest.unwrap_or_default(),
ref_type.as_deref(),
ref_name.as_deref(),
tag_override.as_deref(),
)?,
labels: {
let mut labels = labels.clone();
labels.insert(
format!("{}.for-cross-target", cross::CROSS_LABEL_DOMAIN),
target.triplet.clone(),
);
labels
},
platforms: vec!["linux/amd64".to_string()],
args: build_args.clone(),
no_cache,
cache_from: if !no_cache {
vec![format!(
"type=registry,ref={}",
target.image_name(&repository, "main")
)]
} else {
vec![]
},
},
))
})
.collect::<cross::Result<BTreeMap<_, _>>>()?;

let mut docker_bake = docker::command(engine);
docker_bake.args(&["buildx", "bake"]);
docker_bake.current_dir(&docker_root);

if push {
docker_bake.arg("--push");
} else if no_output {
docker_bake.args(&["--set", "*.output=type=tar,dest=/dev/null"]);
} else {
docker_bake.arg("--load");
}
let defaults = targets
.iter()
.map(|(t, _)| t.clone())
.collect::<Vec<String>>();
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"group": {
"default": {
"targets": defaults,
}
},
"target": targets,
}))?
);
todo!()
}
68 changes: 45 additions & 23 deletions xtask/src/build_docker_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use color_eyre::Section;
use cross::{docker, CommandExt, ToUtf8};
use std::fmt::Write;

use crate::util::ImageTarget;

#[derive(Args, Debug)]
pub struct BuildDockerImage {
#[clap(long, hide = true, env = "GITHUB_REF_TYPE")]
Expand Down Expand Up @@ -44,6 +46,9 @@ pub struct BuildDockerImage {
default_value = "auto"
)]
progress: String,
/// Use a bake build
#[clap(long)]
bake: bool,
/// Do not load from cache when building the image.
#[clap(long)]
no_cache: bool,
Expand All @@ -64,7 +69,7 @@ pub struct BuildDockerImage {
targets: Vec<crate::ImageTarget>,
}

fn locate_dockerfile(
pub fn locate_dockerfile(
target: crate::ImageTarget,
docker_root: &Path,
cross_toolchain_root: &Path,
Expand Down Expand Up @@ -147,6 +152,9 @@ pub fn build_docker_image(
.collect::<cross::Result<Vec<_>>>()?;

let mut results = vec![];
if push && tag_override.is_none() {
panic!("Refusing to push without tag or branch. Specify a repository and tag with `--repository <repository> --tag <tag>`")
}
for (target, dockerfile) in &targets {
if gha && targets.len() > 1 {
println!("::group::Build {target}");
Expand All @@ -163,28 +171,15 @@ pub fn build_docker_image(
docker_build.arg("--load");
}

let mut tags = vec![];

match (ref_type.as_deref(), ref_name.as_deref()) {
(Some(ref_type), Some(ref_name)) => tags.extend(determine_image_name(
target,
&repository,
ref_type,
ref_name,
is_latest.unwrap_or_default(),
&version,
)?),
_ => {
if push && tag_override.is_none() {
panic!("Refusing to push without tag or branch. Specify a repository and tag with `--repository <repository> --tag <tag>`")
}
tags.push(target.image_name(&repository, "local"));
}
}

if let Some(ref tag) = tag_override {
tags = vec![target.image_name(&repository, tag)];
}
let tags: Vec<_> = get_tags(
target,
&repository,
&version,
is_latest.unwrap_or_default(),
ref_type.as_deref(),
ref_name.as_deref(),
tag_override.as_deref(),
)?;

docker_build.arg("--pull");
if no_cache {
Expand Down Expand Up @@ -282,6 +277,33 @@ pub fn build_docker_image(
Ok(())
}

pub fn get_tags(
target: &ImageTarget,
repository: &str,
version: &str,
is_latest: bool,
ref_type: Option<&str>,
ref_name: Option<&str>,
tag_override: Option<&str>,
) -> cross::Result<Vec<String>> {
if let Some(tag) = tag_override {
return Ok(vec![target.image_name(repository, tag)]);
}

let mut tags = vec![];

match (ref_type, ref_name) {
(Some(ref_type), Some(ref_name)) => tags.extend(determine_image_name(
target, repository, ref_type, ref_name, is_latest, version,
)?),
_ => {
tags.push(target.image_name(repository, "local"));
}
}

Ok(tags)
}

pub fn determine_image_name(
target: &crate::ImageTarget,
repository: &str,
Expand Down
Loading

0 comments on commit c5b9ea0

Please sign in to comment.