Skip to content

Commit

Permalink
add way to do pre-builds and to specify build args for dockerfile builds
Browse files Browse the repository at this point in the history
  • Loading branch information
Emilgardis committed Apr 5, 2022
1 parent 8d7a435 commit ecb4542
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 39 deletions.
3 changes: 2 additions & 1 deletion docs/cross_toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ volumes = ["VOL1_ARG", "VOL2_ARG"]
passthrough = ["VAR1", "VAR2"]
xargo = false
image = "test-image"
dockerfile = { file = "./Dockerfile", context = "." } # or just `dockerfile = "./Dockerfile"` if the context is the cargo project root
dockerfile = { file = "./Dockerfile", context = ".", build-args = { ARG1 = "foo" } } # also supports `dockerfile = "./Dockerfile"`
pre-build = ["echo 'Hello world!'"]
runner = "custom-runner"
```
25 changes: 25 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ impl Environment {
self.get_target_var(target, "DOCKERFILE_CONTEXT")
}

fn pre_build(&self, target: &Target) -> Option<Vec<String>> {
self.get_target_var(target, "PRE_BUILD")
.map(|v| v.split('\n').map(String::from).collect())
}

fn runner(&self, target: &Target) -> Option<String> {
self.get_target_var(target, "RUNNER")
}
Expand Down Expand Up @@ -180,6 +185,26 @@ impl Config {
})
}

pub fn dockerfile_build_args(
&self,
target: &Target,
) -> Result<Option<HashMap<String, String>>> {
// This value does not support env variables
self.toml.as_ref().map_or(Ok(None), |t| {
Ok(t.dockerfile_build_args(target).map(ToOwned::to_owned))
})
}

pub fn pre_build(&self, target: &Target) -> Result<Option<Vec<String>>> {
let env_value = self.env.pre_build(target);
if let Some(env_value) = env_value {
return Ok(Some(env_value));
}
self.toml
.as_ref()
.map_or(Ok(None), |t| Ok(t.pre_build(target).map(ToOwned::to_owned)))
}

pub fn runner(&self, target: &Target) -> Result<Option<String>> {
let env_value = self.env.runner(target);
if let Some(env_value) = env_value {
Expand Down
18 changes: 18 additions & 0 deletions src/cross_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct CrossBuildConfig {

/// Target configuration
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct CrossTargetConfig {
#[serde(default)]
passthrough: Vec<String>,
Expand All @@ -35,6 +36,7 @@ pub struct CrossTargetConfig {
image: Option<String>,
#[serde(default, deserialize_with = "opt_string_or_struct")]
dockerfile: Option<CrossTargetConfigDockerfile>,
pre_build: Option<Vec<String>>,
runner: Option<String>,
}

Expand All @@ -43,6 +45,7 @@ pub struct CrossTargetConfig {
pub struct CrossTargetConfigDockerfile {
file: String,
context: Option<String>,
build_args: Option<HashMap<String, String>>,
}

impl FromStr for CrossTargetConfigDockerfile {
Expand All @@ -52,6 +55,7 @@ impl FromStr for CrossTargetConfigDockerfile {
Ok(CrossTargetConfigDockerfile {
file: s.to_string(),
context: None,
build_args: None,
})
}
}
Expand Down Expand Up @@ -91,6 +95,19 @@ impl CrossToml {
.flatten()
}

/// Returns the `target.{}.dockerfile.build_args` part of `Cross.toml`
pub fn dockerfile_build_args(&self, target: &Target) -> Option<&HashMap<String, String>> {
self.get_target(target)
.and_then(|t| t.dockerfile.as_ref())
.map(|d| d.build_args.as_ref())
.flatten()
}

/// Returns the `target.{}.dockerfile.pre-build` part of `Cross.toml`
pub fn pre_build(&self, target: &Target) -> Option<&[String]> {
self.get_target(target).and_then(|t| t.pre_build.as_deref())
}

/// Returns the `target.{}.runner` part of `Cross.toml`
pub fn runner(&self, target: &Target) -> Option<String> {
self.get_target(target).and_then(|t| t.runner.clone())
Expand Down Expand Up @@ -248,6 +265,7 @@ mod tests {
image: Some("test-image".to_string()),
runner: None,
dockerfile: None,
pre_build: None,
},
);

Expand Down
90 changes: 52 additions & 38 deletions src/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,34 @@ pub fn run(
}
}

let image = image_assemble(verbose, config, target, &host_root)
.wrap_err("could not get image to execute inside")?;
let mut image = if let Some(dockerfile) = config.dockerfile(target)? {
Dockerfile::File {
path: &dockerfile,
context: config.dockerfile_context(&target)?.as_deref(),
name: config.image(&target)?.as_deref(),
}
.build(
verbose,
&host_root,
config.dockerfile_build_args(&target)?.unwrap_or_default(),
)
.wrap_err("when building dockerfile")?
} else {
image(config, target)?
};

if let Some(pre_build) = config.pre_build(target)? {
image = Dockerfile::Stdin {
content: format!(
"
FROM {image}
ARG CMD
RUN $CMD"
),
}
.build(verbose, &host_root, Some(("CMD", pre_build.join("\n"))))
.wrap_err("when pre-building")?
}

docker
.arg(image)
Expand All @@ -267,12 +293,17 @@ pub enum Dockerfile<'a> {
name: Option<&'a str>,
},
Stdin {
content: &'a str,
content: String,
},
}

impl<'a> Dockerfile<'a> {
pub fn build(&self, verbose: bool, host_root: &Path) -> Result<String> {
pub fn build(
&self,
verbose: bool,
host_root: &Path,
build_args: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
) -> Result<String> {
let mut docker_build = docker_command("build")?;
docker_build.current_dir(host_root);
docker_build.env("DOCKER_SCAN_SUGGEST", "false");
Expand All @@ -287,33 +318,36 @@ impl<'a> Dockerfile<'a> {
docker_build.args(["--tag", &image]);
};

if let Some(context) = self.context() {
docker_build.arg(&context);
} else {
// This `.` is techically the host_root, not cwd
// When using Dockerfile::Stdin, this will be ignored by docker.
docker_build.arg(".");
for (key, arg) in build_args.into_iter() {
docker_build.args(["--build-arg", &format!("{}={}", key.as_ref(), arg.as_ref())]);
}

match self {
Dockerfile::File { path, .. } => {
docker_build.args(["--file", &path]);
if let Some(context) = self.context() {
docker_build.arg(&context);
}
}
Dockerfile::Stdin { content } => {
// XXX: Ugly hack to circumvent platform issues. we could use echo
// XXX: Ugly hack to circumvent platform issues. we could use echo, but it may not exist
let stdin_path = tempfile::NamedTempFile::new()
.wrap_err("could not create a temporary file")?
.into_temp_path();
let mut file = std::fs::File::options().write(true).open(&stdin_path)?;
file.write_all(content.as_bytes())?;
file.sync_data()?;
.into_temp_path()
.keep()?;
{
let mut file = std::fs::File::options()
.write(true)
.read(true)
.open(&stdin_path)?;
file.write_all(content.as_bytes())?;
}
let file = std::fs::File::open(&stdin_path)?;
docker_build.arg("-").stdin(file);
}
}

docker_build
.run(verbose)
.wrap_err_with(|| format!("build failed"))?;
docker_build.run(verbose)?;

let image_name = if let Some(image) = self.image_name() {
image.to_owned()
Expand Down Expand Up @@ -349,26 +383,6 @@ impl<'a> Dockerfile<'a> {
}
}

/// (Assemble) and return the image to execute inside
pub fn image_assemble(
verbose: bool,
config: &Config,
target: &Target,
host_root: &Path,
) -> Result<String> {
if let Some(dockerfile) = config.dockerfile(target)? {
Dockerfile::File {
path: &dockerfile,
context: config.dockerfile_context(&target)?.as_deref(),
name: config.image(&target)?.as_deref(),
}
.build(verbose, host_root)
.wrap_err("when building dockerfile")
} else {
Ok(image(config, target)?)
}
}

pub fn image(config: &Config, target: &Target) -> Result<String> {
if let Some(image) = config.image(target)? {
return Ok(image);
Expand Down

0 comments on commit ecb4542

Please sign in to comment.