diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 715269afc..97c428314 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -208,6 +208,7 @@ jobs: - { target: thumbv7em-none-eabihf, os: ubuntu-latest, std: 1 } - { target: thumbv7m-none-eabi, os: ubuntu-latest, std: 1 } - { target: cross, os: ubuntu-latest } + - { target: zig, os: ubuntu-latest } build: name: target (${{ matrix.pretty }},${{ matrix.os }}) @@ -295,6 +296,12 @@ jobs: RUN: ${{ matrix.run }} RUNNERS: ${{ matrix.runners }} shell: bash + + - name: Test Zig Image + if: steps.prepare-meta.outputs.has-image && steps.prepare-meta.outputs.test-variant == 'zig' + run: ./ci/test-zig-image.sh + shell: bash + - uses: ./.github/actions/cargo-install-upload-artifacts if: matrix.deploy with: diff --git a/CHANGELOG.md b/CHANGELOG.md index ba3e71345..c384ce675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #905 - added `qemu-runner` for musl images, allowing use of native or emulated runners. - #905 - added qemu emulation to `i586-unknown-linux-gnu`, `i686-unknown-linux-musl`, and `i586-unknown-linux-gnu`, so they can run on an `x86` CPU, rather than an `x86_64` CPU. - #900 - add the option to skip copying build artifacts back to host when using remote cross via `CROSS_REMOTE_SKIP_BUILD_ARTIFACTS`. -- #891 - support custom user namespace overrides by setting the `CROSS_CONTAINER_USER_NAMESPACE` environment variable. +- #891 - support custom user namespace overrides by setting the `CROSS_CONTAINER_USER_NAMESPACE` environment variable. - #890 - support rootless docker via the `CROSS_ROOTLESS_CONTAINER_ENGINE` environment variable. +- #880 - added a zig-based image, allowing multiple targets to be built from the same image, using cargo-zigbuild. - #878 - added an image `ghcr.io/cross-rs/cross` containing cross. +======= ### Changed diff --git a/ci/test-zig-image.sh b/ci/test-zig-image.sh new file mode 100755 index 000000000..a2c8d30ad --- /dev/null +++ b/ci/test-zig-image.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2086,SC1091,SC1090 + +set -x +set -eo pipefail + +# NOTE: "${@}" is an unbound variable for bash 3.2, which is the +# installed version on macOS. likewise, "${var[@]}" is an unbound +# error if var is an empty array. + +ci_dir=$(dirname "${BASH_SOURCE[0]}") +ci_dir=$(realpath "${ci_dir}") +project_home=$(dirname "${ci_dir}") +. "${ci_dir}"/shared.sh + +# zig cc is very slow: only use a few targets. +TARGETS=( + "aarch64-unknown-linux-gnu" + "aarch64-unknown-linux-musl" + "i586-unknown-linux-gnu" + "i586-unknown-linux-musl" +) + +# on CI, it sets `CROSS_TARGET_ZIG_IMAGE` rather than `CROSS_TARGET_ZIG_IMAGE` +if [[ -n "${CROSS_TARGET_ZIG_IMAGE}" ]]; then + export CROSS_BUILD_ZIG_IMAGE="${CROSS_TARGET_ZIG_IMAGE}" + unset CROSS_TARGET_ZIG_IMAGE +fi + +main() { + export CROSS_BUILD_ZIG=1 + + local td= + local target= + + retry cargo fetch + cargo build + export CROSS="${project_home}/target/debug/cross" + + td="$(mktemp -d)" + git clone --depth 1 https://github.com/cross-rs/rust-cpp-hello-word "${td}" + pushd "${td}" + + for target in "${TARGETS[@]}"; do + "${CROSS}" build --target "${target}" --verbose + # note: ensure #724 doesn't replicate during CI. + # https://github.com/cross-rs/cross/issues/724 + cargo clean + done + + popd + rm -rf "${td}" +} + +main "${@}" diff --git a/docker/Dockerfile.zig b/docker/Dockerfile.zig new file mode 100644 index 000000000..ebca27452 --- /dev/null +++ b/docker/Dockerfile.zig @@ -0,0 +1,22 @@ +FROM ubuntu:20.04 +ARG DEBIAN_FRONTEND=noninteractive + +COPY common.sh lib.sh / +RUN /common.sh + +COPY cmake.sh / +RUN /cmake.sh + +COPY xargo.sh / +RUN /xargo.sh + +ARG TARGETPLATFORM +COPY zig.sh / +RUN /zig.sh $TARGETPLATFORM + +# we don't export `BINDGEN_EXTRA_CLANG_ARGS`, `QEMU_LD_PREFIX`, or +# `PKG_CONFIG_PATH` since zig doesn't have a traditional sysroot structure, +# and we're not using standard, shared packages. none of the packages +# have runners either, since they do not ship with the required +# dynamic linker (`ld-linux-${arch}.so`). +ENV PATH=$PATH:/opt/zig diff --git a/docker/zig.sh b/docker/zig.sh new file mode 100755 index 000000000..d41ee3742 --- /dev/null +++ b/docker/zig.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +set -x +set -eo pipefail + +# shellcheck disable=SC1091 +. lib.sh + +main() { + local platform="${1}" + install_packages ca-certificates curl xz-utils + + install_zig "${platform}" + install_zigbuild "${platform}" + + purge_packages + rm "${0}" +} + +install_zig() { + local platform="${1}" + local version="0.9.1" + local dst="/opt/zig" + local arch= + local os= + local triple= + + case "${platform}" in + 'linux/386') + arch="i386" + os="linux" + ;; + 'linux/amd64') + arch="x86_64" + os="linux" + ;; + 'linux/arm64') + arch="aarch64" + os="linux" + ;; + 'linux/riscv64') + arch="riscv64" + os="linux" + ;; + 'linux/ppc64le') + triple="powerpc64le-linux-gnu" + ;; + 'linux/s390x') + triple="s390x-linux-gnu" + ;; + 'darwin/amd64') + arch="x86_64" + os="macos" + ;; + 'darwin/arm64') + arch="aarch64" + os="macos" + ;; + # NOTE: explicitly don't support linux/arm/v6 + *) + echo "Unsupported target platform '${platform}'" 1>&2 + exit 1 + ;; + esac + + if [[ -n "${arch}" ]]; then + install_zig_tarball "${arch}" "${os}" "${version}" "${dst}" + else + install_zig_source "${triple}" "${version}" "${dst}" + fi +} + +install_zig_tarball() { + local arch="${1}" + local os="${2}" + local version="${3}" + local dst="${4}" + local filename="zig-${os}-${arch}-${version}.tar.xz" + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://ziglang.org/download/${version}/${filename}" -O + mkdir -p "${dst}" + tar --strip-components=1 -xJf "${filename}" --directory "${dst}" + + popd + + rm -rf "${td}" +} + +install_zig_source() { + local triple="${1}" + local version="${2}" + local dst="${3}" + local filename="zig-bootstrap-${version}.tar.xz" + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "https://ziglang.org/download/${version}/${filename}" -O + mkdir zig + tar --strip-components=1 -xJf "${filename}" --directory zig + + pushd zig + install_packages python3 make g++ + ./build -j5 "${triple}" native + mv "out/zig-${triple}-native" /opt/zig + + popd + popd + + rm -rf "${td}" +} + +install_zigbuild() { + local platform="${1}" + local version=0.11.0 + local dst="/usr/local" + local triple= + + # we don't know if `linux/arm/v7` is hard-float, + # and we don't know the the zigbuild `apple-darwin` + # target doesn't manually specify the architecture. + case "${platform}" in + 'linux/386') + triple="i686-unknown-linux-musl" + ;; + 'linux/amd64') + triple="x86_64-unknown-linux-musl" + ;; + 'linux/arm64') + triple="aarch64-unknown-linux-musl" + ;; + *) + ;; + esac + + if [[ -n "${triple}" ]]; then + install_zigbuild_tarball "${triple}" "${version}" "${dst}" + else + install_zigbuild_source "${version}" "${dst}" + fi +} + +install_zigbuild_tarball() { + local triple="${1}" + local version="${2}" + local dst="${3}" + local repo="https://github.com/messense/cargo-zigbuild" + local filename="cargo-zigbuild-v${version}.${triple}.tar.gz" + + local td + td="$(mktemp -d)" + + pushd "${td}" + + curl --retry 3 -sSfL "${repo}/releases/download/v${version}/${filename}" -O + mkdir -p "${dst}/bin" + tar -xzf "${filename}" --directory "${dst}/bin" + + popd + + rm -rf "${td}" +} + +install_zigbuild_source() { + local version="${1}" + local dst="${2}" + + local td + td="$(mktemp -d)" + + pushd "${td}" + + export RUSTUP_HOME="${td}/rustup" + export CARGO_HOME="${td}/cargo" + + curl --retry 3 -sSfL https://sh.rustup.rs -o rustup-init.sh + sh rustup-init.sh -y --no-modify-path --profile minimal + + PATH="${CARGO_HOME}/bin:${PATH}" \ + cargo install cargo-zigbuild \ + --version "${version}" \ + --root "${dst}" \ + --locked + + popd + + rm -rf "${td}" +} + +main "${@}" diff --git a/docs/cross_toml.md b/docs/cross_toml.md index 51f878302..bde0ce189 100644 --- a/docs/cross_toml.md +++ b/docs/cross_toml.md @@ -33,6 +33,7 @@ The `target` key allows you to specify parameters for specific compilation targe [target.aarch64-unknown-linux-gnu] xargo = false build-std = false +zig = "2.17" image = "test-image" pre-build = ["apt-get update"] runner = "custom-runner" @@ -64,3 +65,26 @@ also supports [target.x86_64-unknown-linux-gnu] dockerfile = "./Dockerfile" ``` + +# `target.TARGET.zig` + +```toml +[target.x86_64-unknown-linux-gnu.zig] +enable = true # enable use of the zig image +version = "2.17" # glibc version to use +image = "zig:local" # custom zig image to use +``` + +also supports + +```toml +[target.x86_64-unknown-linux-gnu] +zig = true +``` + +or + +```toml +[target.x86_64-unknown-linux-gnu] +zig = "2.17" +``` diff --git a/src/config.rs b/src/config.rs index 4e9fbd2f2..d909d5dfe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -65,6 +65,20 @@ impl Environment { self.get_values_for("BUILD_STD", target, bool_from_envvar) } + fn zig(&self, target: &Target) -> (Option, Option) { + self.get_values_for("ZIG", target, bool_from_envvar) + } + + fn zig_version(&self, target: &Target) -> Option { + let res = self.get_values_for("ZIG_VERSION", target, str::to_string); + res.0.or(res.1) + } + + fn zig_image(&self, target: &Target) -> Option { + let res = self.get_values_for("ZIG_IMAGE", target, str::to_string); + res.0.or(res.1) + } + fn image(&self, target: &Target) -> Option { self.get_target_var(target, "IMAGE") } @@ -266,6 +280,18 @@ impl Config { self.bool_from_config(target, Environment::build_std, CrossToml::build_std) } + pub fn zig(&self, target: &Target) -> Option { + self.bool_from_config(target, Environment::zig, CrossToml::zig) + } + + pub fn zig_version(&self, target: &Target) -> Result> { + self.string_from_config(target, Environment::zig_version, CrossToml::zig_version) + } + + pub fn zig_image(&self, target: &Target) -> Result> { + self.string_from_config(target, Environment::zig_image, CrossToml::zig_image) + } + pub fn image(&self, target: &Target) -> Result> { self.string_from_config(target, Environment::image, CrossToml::image) } @@ -409,20 +435,29 @@ mod tests { let mut map = std::collections::HashMap::new(); map.insert("CROSS_BUILD_XARGO", "tru"); map.insert("CROSS_BUILD_STD", "false"); + map.insert("CROSS_BUILD_ZIG_IMAGE", "zig:local"); let env = Environment::new(Some(map)); assert_eq!(env.xargo(&target()), (Some(true), None)); assert_eq!(env.build_std(&target()), (Some(false), None)); + assert_eq!(env.zig(&target()), (None, None)); + assert_eq!(env.zig_version(&target()), None); + assert_eq!(env.zig_image(&target()), Some("zig:local".to_string())); } #[test] pub fn build_and_target_set_returns_tuple() { let mut map = std::collections::HashMap::new(); map.insert("CROSS_BUILD_XARGO", "true"); + map.insert("CROSS_BUILD_ZIG", "true"); + map.insert("CROSS_BUILD_ZIG_VERSION", "2.17"); map.insert("CROSS_TARGET_AARCH64_UNKNOWN_LINUX_GNU_XARGO", "false"); let env = Environment::new(Some(map)); assert_eq!(env.xargo(&target()), (Some(true), Some(false))); + assert_eq!(env.zig(&target()), (Some(true), None)); + assert_eq!(env.zig_version(&target()), Some("2.17".into())); + assert_eq!(env.zig_image(&target()), None); } #[test] diff --git a/src/cross_toml.rs b/src/cross_toml.rs index 2768cf629..f9bc4369c 100644 --- a/src/cross_toml.rs +++ b/src/cross_toml.rs @@ -23,6 +23,8 @@ pub struct CrossBuildConfig { env: CrossEnvConfig, xargo: Option, build_std: Option, + #[serde(default, deserialize_with = "opt_string_bool_or_struct")] + zig: Option, default_target: Option, pre_build: Option>, #[serde(default, deserialize_with = "opt_string_or_struct")] @@ -35,6 +37,8 @@ pub struct CrossBuildConfig { pub struct CrossTargetConfig { xargo: Option, build_std: Option, + #[serde(default, deserialize_with = "opt_string_bool_or_struct")] + zig: Option, image: Option, #[serde(default, deserialize_with = "opt_string_or_struct")] dockerfile: Option, @@ -65,6 +69,43 @@ impl FromStr for CrossTargetDockerfileConfig { } } +/// Zig configuration +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub struct CrossZigConfig { + enable: Option, + version: Option, + image: Option, +} + +impl From<&str> for CrossZigConfig { + fn from(s: &str) -> CrossZigConfig { + CrossZigConfig { + enable: Some(true), + version: Some(s.to_string()), + image: None, + } + } +} + +impl From for CrossZigConfig { + fn from(s: bool) -> CrossZigConfig { + CrossZigConfig { + enable: Some(s), + version: None, + image: None, + } + } +} + +impl FromStr for CrossZigConfig { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result { + Ok(s.into()) + } +} + /// Cross configuration #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)] pub struct CrossToml { @@ -274,6 +315,33 @@ impl CrossToml { self.get_value(target, |b| b.build_std, |t| t.build_std) } + /// Returns the `{}.zig` or `{}.zig.version` part of `Cross.toml` + pub fn zig(&self, target: &Target) -> (Option, Option) { + self.get_value( + target, + |b| b.zig.as_ref().and_then(|z| z.enable), + |t| t.zig.as_ref().and_then(|z| z.enable), + ) + } + + /// Returns the `{}.zig` or `{}.zig.version` part of `Cross.toml` + pub fn zig_version(&self, target: &Target) -> Option { + self.get_string( + target, + |b| b.zig.as_ref().and_then(|c| c.version.as_ref()), + |t| t.zig.as_ref().and_then(|c| c.version.as_ref()), + ) + } + + /// Returns the `{}.zig.image` part of `Cross.toml` + pub fn zig_image(&self, target: &Target) -> Option { + self.get_string( + target, + |b| b.zig.as_ref().and_then(|c| c.image.as_ref()), + |t| t.zig.as_ref().and_then(|c| c.image.as_ref()), + ) + } + /// Returns the list of environment variables to pass through for `build` and `target` pub fn env_passthrough(&self, target: &Target) -> (Option<&[String]>, Option<&[String]>) { self.get_ref( @@ -395,6 +463,68 @@ where deserializer.deserialize_any(StringOrStruct(PhantomData)) } +fn opt_string_bool_or_struct<'de, T, D>(deserializer: D) -> Result, D::Error> +where + T: Deserialize<'de> + From + std::str::FromStr, + D: serde::Deserializer<'de>, +{ + use std::{fmt, marker::PhantomData}; + + use serde::de::{self, MapAccess, Visitor}; + + struct StringBoolOrStruct(PhantomData T>); + + impl<'de, T> Visitor<'de> for StringBoolOrStruct + where + T: Deserialize<'de> + From + std::str::FromStr, + { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("string, bool, or map") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(Some(From::from(value))) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(FromStr::from_str(value).ok()) + } + + fn visit_map(self, map: M) -> Result + where + M: MapAccess<'de>, + { + let t: Result = + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); + t.map(Some) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_any(StringBoolOrStruct(PhantomData)) +} + #[cfg(test)] mod tests { use super::*; @@ -437,6 +567,7 @@ mod tests { }, xargo: Some(true), build_std: None, + zig: None, default_target: None, pre_build: Some(vec![s!("echo 'Hello World!'")]), dockerfile: None, @@ -474,6 +605,7 @@ mod tests { }, xargo: Some(false), build_std: Some(true), + zig: None, image: Some(s!("test-image")), runner: None, dockerfile: None, @@ -514,6 +646,11 @@ mod tests { CrossTargetConfig { xargo: Some(false), build_std: None, + zig: Some(CrossZigConfig { + enable: None, + version: None, + image: Some("zig:local".to_string()), + }), image: None, dockerfile: Some(CrossTargetDockerfileConfig { file: s!("Dockerfile.test"), @@ -538,6 +675,11 @@ mod tests { }, xargo: Some(true), build_std: None, + zig: Some(CrossZigConfig { + enable: Some(true), + version: Some("2.17".to_string()), + image: None, + }), default_target: None, pre_build: Some(vec![]), dockerfile: None, @@ -547,6 +689,7 @@ mod tests { let test_str = r#" [build] xargo = true + zig = "2.17" pre-build = [] [build.env] @@ -557,6 +700,9 @@ mod tests { dockerfile = "Dockerfile.test" pre-build = ["echo 'Hello'"] + [target.aarch64-unknown-linux-gnu.zig] + image = "zig:local" + [target.aarch64-unknown-linux-gnu.env] volumes = ["VOL"] "#; @@ -596,6 +742,7 @@ mod tests { }, build_std: None, xargo: Some(true), + zig: None, default_target: None, pre_build: None, dockerfile: None, diff --git a/src/docker/custom.rs b/src/docker/custom.rs index 0c950ada7..3e360955b 100644 --- a/src/docker/custom.rs +++ b/src/docker/custom.rs @@ -28,6 +28,7 @@ impl<'a> Dockerfile<'a> { options: &DockerOptions, paths: &DockerPaths, build_args: impl IntoIterator, impl AsRef)>, + uses_zig: bool, msg_info: &mut MessageInfo, ) -> Result { let mut docker_build = docker::subcommand(&options.engine, "build"); @@ -79,7 +80,9 @@ impl<'a> Dockerfile<'a> { }; if matches!(self, Dockerfile::File { .. }) { - if let Ok(cross_base_image) = self::image_name(&options.config, &options.target) { + if let Ok(cross_base_image) = + self::image_name(&options.config, &options.target, uses_zig) + { docker_build.args([ "--build-arg", &format!("CROSS_BASE_IMAGE={cross_base_image}"), diff --git a/src/docker/local.rs b/src/docker/local.rs index 2d86d3d07..429d4c31e 100644 --- a/src/docker/local.rs +++ b/src/docker/local.rs @@ -17,12 +17,18 @@ pub(crate) fn run( let engine = &options.engine; let dirs = &paths.directories; - let mut cmd = cargo_safe_command(options.uses_xargo); + let mut cmd = cargo_safe_command(options.cargo_variant); cmd.args(args); let mut docker = subcommand(engine, "run"); docker_userns(&mut docker); - docker_envvars(&mut docker, &options.config, &options.target, msg_info)?; + docker_envvars( + &mut docker, + &options.config, + &options.target, + options.cargo_variant.uses_zig(), + msg_info, + )?; let mount_volumes = docker_mount( &mut docker, diff --git a/src/docker/remote.rs b/src/docker/remote.rs index 100290dc9..d05beb7b2 100644 --- a/src/docker/remote.rs +++ b/src/docker/remote.rs @@ -840,7 +840,13 @@ pub(crate) fn run( docker_userns(&mut docker); docker.args(&["--name", &container]); docker.args(&["-v", &format!("{}:{mount_prefix}", volume.as_ref())]); - docker_envvars(&mut docker, &options.config, target, msg_info)?; + docker_envvars( + &mut docker, + &options.config, + target, + options.cargo_variant.uses_zig(), + msg_info, + )?; let mut volumes = vec![]; let mount_volumes = docker_mount( @@ -871,7 +877,11 @@ pub(crate) fn run( } docker - .arg(&image_name(&options.config, target)?) + .arg(&image_name( + &options.config, + target, + options.cargo_variant.uses_zig(), + )?) // ensure the process never exits until we stop it .args(&["sh", "-c", "sleep infinity"]) .run_and_get_status(msg_info, true)?; @@ -1046,7 +1056,7 @@ pub(crate) fn run( final_args.push("--target-dir".to_owned()); final_args.push(target_dir_string); } - let mut cmd = cargo_safe_command(options.uses_xargo); + let mut cmd = cargo_safe_command(options.cargo_variant); cmd.args(final_args); // 5. create symlinks for copied data diff --git a/src/docker/shared.rs b/src/docker/shared.rs index 696817f74..100cc66b8 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -13,7 +13,7 @@ use crate::file::{self, write_file, PathExt, ToUtf8}; use crate::id; use crate::rustc::{self, VersionMetaExt}; use crate::shell::{MessageInfo, Verbosity}; -use crate::Target; +use crate::{CargoVariant, Target}; pub use super::custom::CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX; @@ -33,15 +33,23 @@ pub struct DockerOptions { pub engine: Engine, pub target: Target, pub config: Config, + pub cargo_variant: CargoVariant, pub uses_xargo: bool, } impl DockerOptions { - pub fn new(engine: Engine, target: Target, config: Config, uses_xargo: bool) -> DockerOptions { + pub fn new( + engine: Engine, + target: Target, + config: Config, + cargo_variant: CargoVariant, + uses_xargo: bool, + ) -> DockerOptions { DockerOptions { engine, target, config, + cargo_variant, uses_xargo, } } @@ -75,7 +83,8 @@ impl DockerOptions { paths: &DockerPaths, msg_info: &mut MessageInfo, ) -> Result { - let mut image = image_name(&self.config, &self.target)?; + let uses_zig = self.cargo_variant.uses_zig(); + let mut image = image_name(&self.config, &self.target, uses_zig)?; if let Some(path) = self.config.dockerfile(&self.target)? { let context = self.config.dockerfile_context(&self.target)?; @@ -94,6 +103,7 @@ impl DockerOptions { self.config .dockerfile_build_args(&self.target)? .unwrap_or_default(), + uses_zig, msg_info, ) .wrap_err("when building dockerfile")?; @@ -116,6 +126,7 @@ impl DockerOptions { self, paths, Some(("CROSS_CMD", pre_build.join("\n"))), + uses_zig, msg_info, ) .wrap_err("when pre-building") @@ -376,12 +387,8 @@ pub fn parse_docker_opts(value: &str) -> Result> { shell_words::split(value).wrap_err_with(|| format!("could not parse docker opts of {}", value)) } -pub(crate) fn cargo_safe_command(uses_xargo: bool) -> SafeCommand { - if uses_xargo { - SafeCommand::new("xargo") - } else { - SafeCommand::new("cargo") - } +pub(crate) fn cargo_safe_command(cargo_variant: CargoVariant) -> SafeCommand { + SafeCommand::new(cargo_variant.to_str()) } fn add_cargo_configuration_envvars(docker: &mut Command) { @@ -432,6 +439,7 @@ pub(crate) fn docker_envvars( docker: &mut Command, config: &Config, target: &Target, + uses_zig: bool, msg_info: &mut MessageInfo, ) -> Result<()> { for ref var in config.env_passthrough(target)?.unwrap_or_default() { @@ -450,6 +458,10 @@ pub(crate) fn docker_envvars( .args(&["-e", "CARGO_HOME=/cargo"]) .args(&["-e", "CARGO_TARGET_DIR=/target"]) .args(&["-e", &cross_runner]); + if uses_zig { + // otherwise, zig have a permissions error trying to create the cache + docker.args(&["-e", "XDG_CACHE_HOME=/target/.zig-cache"]); + } add_cargo_configuration_envvars(docker); if let Some(username) = id::username().wrap_err("could not get username")? { @@ -625,7 +637,7 @@ pub(crate) fn docker_seccomp( Ok(()) } -pub(crate) fn image_name(config: &Config, target: &Target) -> Result { +pub(crate) fn image_name(config: &Config, target: &Target, uses_zig: bool) -> Result { if let Some(image) = config.image(target)? { return Ok(image); } @@ -643,7 +655,16 @@ pub(crate) fn image_name(config: &Config, target: &Target) -> Result { "main" }; - Ok(format!("{CROSS_IMAGE}/{target}:{version}")) + let name = if uses_zig { + if let Some(image) = config.zig_image(target)? { + return Ok(image); + } + "zig" + } else { + target.triple() + }; + + Ok(format!("{CROSS_IMAGE}/{name}:{version}")) } fn docker_read_mount_paths(engine: &Engine) -> Result> { diff --git a/src/lib.rs b/src/lib.rs index 57fdcc45e..9a3b498a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -330,11 +330,11 @@ impl std::fmt::Display for Target { } impl Target { - pub fn from(triple: &str, target_list: &TargetList) -> Target { - if target_list.contains(triple) { - Target::new_built_in(triple) + pub fn from(target: &str, target_list: &TargetList) -> Target { + if target_list.contains(target) { + Target::new_built_in(target) } else { - Target::new_custom(triple) + Target::new_custom(target) } } } @@ -367,11 +367,42 @@ impl From for Target { impl Serialize for Target { fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CargoVariant { + Cargo, + Xargo, + Zig, +} + +impl CargoVariant { + pub fn create(uses_zig: bool, uses_xargo: bool) -> Result { + match (uses_zig, uses_xargo) { + (true, true) => eyre::bail!("cannot use both zig and xargo"), + (true, false) => Ok(CargoVariant::Zig), + (false, true) => Ok(CargoVariant::Xargo), + (false, false) => Ok(CargoVariant::Cargo), + } + } + + pub fn to_str(self) -> &'static str { match self { - Target::BuiltIn { triple } => serializer.serialize_str(triple), - Target::Custom { triple } => serializer.serialize_str(triple), + CargoVariant::Cargo => "cargo", + CargoVariant::Xargo => "xargo", + CargoVariant::Zig => "cargo-zigbuild", } } + + pub fn uses_xargo(self) -> bool { + self == CargoVariant::Xargo + } + + pub fn uses_zig(self) -> bool { + self == CargoVariant::Zig + } } fn warn_on_failure(target: &Target, toolchain: &str, msg_info: &mut MessageInfo) -> Result<()> { @@ -416,7 +447,9 @@ pub fn run() -> Result { .unwrap_or_else(|| Target::from(host.triple(), &target_list)); config.confusable_target(&target, &mut msg_info)?; - let image_exists = match docker::image_name(&config, &target) { + let uses_zig = config.zig(&target).unwrap_or(false); + let zig_version = config.zig_version(&target)?; + let image_exists = match docker::image_name(&config, &target, uses_zig) { Ok(_) => true, Err(err) => { msg_info.warn(err)?; @@ -449,6 +482,7 @@ pub fn run() -> Result { let uses_build_std = config.build_std(&target).unwrap_or(false); let uses_xargo = !uses_build_std && config.xargo(&target).unwrap_or(!target.is_builtin()); + let cargo_variant = CargoVariant::create(uses_zig, uses_xargo)?; if !config.custom_toolchain() { // build-std overrides xargo, but only use it if it's a built-in // tool but not an available target or doesn't have rust-std. @@ -498,7 +532,12 @@ pub fn run() -> Result { } else if !args.all.iter().any(|a| a.starts_with("--target")) { let mut args_with_target = args.all.clone(); args_with_target.push("--target".to_owned()); - args_with_target.push(target.triple().to_owned()); + let mut target_and_libc = target.triple().to_owned(); + if let Some(libc) = zig_version { + target_and_libc.push('.'); + target_and_libc.push_str(&libc); + } + args_with_target.push(target_and_libc); args_with_target } else { args.all.clone() @@ -527,8 +566,13 @@ pub fn run() -> Result { } let paths = docker::DockerPaths::create(&engine, metadata, cwd, sysroot)?; - let options = - docker::DockerOptions::new(engine, target.clone(), config, uses_xargo); + let options = docker::DockerOptions::new( + engine, + target.clone(), + config, + cargo_variant, + uses_xargo, + ); let status = docker::run(options, paths, &filtered_args, &mut msg_info) .wrap_err("could not run container")?; let needs_host = args.subcommand.map_or(false, |sc| sc.needs_host(is_remote)); diff --git a/xtask/src/util.rs b/xtask/src/util.rs index b5884660d..814ee1838 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -132,7 +132,7 @@ impl ImageTarget { /// Determine if this target uses the default test script pub fn is_default_test_image(&self) -> bool { - self.triplet != "cross" + !matches!(self.triplet.as_str(), "cross" | "zig") } /// Determine if this target needs to interact with the project root.