From 0d112fc04c576b903fd03726e542dbffd67ffead Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Thu, 30 Jun 2022 15:53:03 -0500 Subject: [PATCH] Add a `zig cc`-based image. Uses cargo-zigbuild as a backend, and adds configuration options for zig under `[build.zig]` and `[target.(...).zig]`. If enabled, and an image override is not provided, `cross` will always use the `zig` image. The feature can be enabled by providing `zig` as a table, bool, or string. It supports custom glibc versions by passing the `zig.version` key, and `zig` can be separately enabled or disabled by providing `zig.enable`. ``` [target.x86_64-unknown-linux-gnu.zig] enable = true # enable use of the zig image version = "2.17" # glibc version to use image = "ghcr.io/cross-rs/zig:local" # custom image to use ``` If provided as a bool, it will use the default glibc version: ``` [target.x86_64-unknown-linux-gnu] \# equivalent to { enable = true } zig = true ``` If provided as a string, `zig` will be automatically enabled: ``` [target.x86_64-unknown-linux-gnu] \# equivalent to { enable = true, version = "2.17" } zig = "2.17" ``` The image does not provide runners, `bindgen` Clang args, or `pkg-config` paths, since `zig cc` does not provide the dynamic library loader (`ld-linux*.so`) required, meaning none of the binaries can be run. For `bindgen`, `zig cc` has an unusual directory structure, so there is no traditional sysroot with `usr`, `lib`, and `include` subdirectories. Finally, since we don't have system packages we can work with, exporting a `pkg-config` path makes little sense. Closes #860. --- .github/workflows/ci.yml | 9 +- CHANGELOG.md | 1 + ci/shared.sh | 23 +++++ ci/test-zig-image.sh | 55 +++++++++++ ci/test.sh | 25 +---- docker/Dockerfile.zig | 22 +++++ docker/zig.sh | 197 +++++++++++++++++++++++++++++++++++++++ docs/cross_toml.md | 24 +++++ src/config.rs | 36 ++++++- src/cross_toml.rs | 147 +++++++++++++++++++++++++++++ src/docker/custom.rs | 3 +- src/docker/local.rs | 28 ++++-- src/docker/mod.rs | 8 +- src/docker/remote.rs | 16 +++- src/docker/shared.rs | 33 +++++-- src/lib.rs | 57 +++++++++-- xtask/src/ci.rs | 5 + xtask/src/util.rs | 5 + 18 files changed, 633 insertions(+), 61 deletions(-) create mode 100644 ci/shared.sh create mode 100755 ci/test-zig-image.sh create mode 100644 docker/Dockerfile.zig create mode 100755 docker/zig.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b521928dd..947625fda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -207,6 +207,7 @@ jobs: - { target: thumbv7em-none-eabi, os: ubuntu-latest, std: 1 } - { target: thumbv7em-none-eabihf, os: ubuntu-latest, std: 1 } - { target: thumbv7m-none-eabi, os: ubuntu-latest, std: 1 } + - { target: zig, os: ubuntu-latest } build: name: target (${{ matrix.pretty }},${{ matrix.os }}) @@ -283,7 +284,7 @@ jobs: IMAGE: ${{ steps.build-docker-image.outputs.image }} shell: bash - name: Test Image - if: steps.prepare-meta.outputs.has-image + if: steps.prepare-meta.outputs.has-image && steps.prepare-meta.outputs.test-variant == 'default' run: ./ci/test.sh env: TARGET: ${{ matrix.target }} @@ -294,6 +295,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 3c6532389..69ff62263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #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. ### Changed diff --git a/ci/shared.sh b/ci/shared.sh new file mode 100644 index 000000000..457861dd7 --- /dev/null +++ b/ci/shared.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +function retry { + local tries="${TRIES-5}" + local timeout="${TIMEOUT-1}" + local try=0 + local exit_code=0 + + while (( try < tries )); do + if "${@}"; then + return 0 + else + exit_code=$? + fi + + sleep "${timeout}" + echo "Retrying ..." 1>&2 + try=$(( try + 1 )) + timeout=$(( timeout * 2 )) + done + + return ${exit_code} +} 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/ci/test.sh b/ci/test.sh index 4b9b494e1..fbfaddaa5 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# shellcheck disable=SC2086 +# shellcheck disable=SC2086,SC1091,SC1090 set -x set -euo pipefail @@ -10,30 +10,9 @@ set -euo pipefail ci_dir=$(dirname "${BASH_SOURCE[0]}") ci_dir=$(realpath "${ci_dir}") +. "${ci_dir}"/shared.sh project_home=$(dirname "${ci_dir}") -function retry { - local tries="${TRIES-5}" - local timeout="${TIMEOUT-1}" - local try=0 - local exit_code=0 - - while (( try < tries )); do - if "${@}"; then - return 0 - else - exit_code=$? - fi - - sleep "${timeout}" - echo "Retrying ..." 1>&2 - try=$(( try + 1 )) - timeout=$(( timeout * 2 )) - done - - return ${exit_code} -} - workspace_test() { "${CROSS[@]}" build --target "${TARGET}" --workspace "$@" ${CROSS_FLAGS} "${CROSS[@]}" run --target "${TARGET}" -p binary "$@" ${CROSS_FLAGS} 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 4c2ae734d..959a8515e 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") } @@ -244,6 +258,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) } @@ -378,20 +404,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] @@ -527,7 +562,6 @@ mod tests { config.env_volumes(&target())?, Some(vec![s!("VOLUME3"), s!("VOLUME4")]) ); - // TODO(ahuszagh) Need volumes Ok(()) } diff --git a/src/cross_toml.rs b/src/cross_toml.rs index 0eed16216..5ce1990f5 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 { @@ -277,6 +318,33 @@ impl CrossToml { self.get_bool(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_bool( + 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_vec( @@ -402,6 +470,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::*; @@ -441,6 +571,7 @@ mod tests { }, xargo: Some(true), build_std: None, + zig: None, default_target: None, pre_build: Some(vec!["echo 'Hello World!'".to_string()]), dockerfile: None, @@ -478,6 +609,7 @@ mod tests { }, xargo: Some(false), build_std: Some(true), + zig: None, image: Some("test-image".to_string()), runner: None, dockerfile: None, @@ -518,6 +650,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: "Dockerfile.test".to_string(), @@ -542,6 +679,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, @@ -551,6 +693,7 @@ mod tests { let test_str = r#" [build] xargo = true + zig = "2.17" pre-build = [] [build.env] @@ -561,6 +704,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"] "#; @@ -600,6 +746,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 5079de5ea..cf378320d 100644 --- a/src/docker/custom.rs +++ b/src/docker/custom.rs @@ -33,6 +33,7 @@ impl<'a> Dockerfile<'a> { build_args: impl IntoIterator, impl AsRef)>, target_triple: &Target, msg_info: MessageInfo, + uses_zig: bool, ) -> Result { let mut docker_build = docker::subcommand(engine, "build"); docker_build.current_dir(host_root); @@ -81,7 +82,7 @@ impl<'a> Dockerfile<'a> { }; if matches!(self, Dockerfile::File { .. }) { - if let Ok(cross_base_image) = self::image_name(config, target_triple) { + if let Ok(cross_base_image) = self::image_name(config, target_triple, 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 360272abf..621e54e5c 100644 --- a/src/docker/local.rs +++ b/src/docker/local.rs @@ -9,7 +9,7 @@ use crate::errors::Result; use crate::extensions::CommandExt; use crate::file::{PathExt, ToUtf8}; use crate::shell::{MessageInfo, Stream}; -use crate::{Config, Target}; +use crate::{CargoVariant, Config, Target}; use eyre::Context; #[allow(clippy::too_many_arguments)] // TODO: refactor @@ -19,7 +19,7 @@ pub(crate) fn run( args: &[String], metadata: &CargoMetadata, config: &Config, - uses_xargo: bool, + cargo_variant: CargoVariant, sysroot: &Path, msg_info: MessageInfo, docker_in_docker: bool, @@ -27,12 +27,18 @@ pub(crate) fn run( ) -> Result { let dirs = Directories::create(engine, metadata, cwd, sysroot, docker_in_docker)?; - let mut cmd = cargo_safe_command(uses_xargo); + let mut cmd = cargo_safe_command(cargo_variant); cmd.args(args); let mut docker = subcommand(engine, "run"); docker_userns(&mut docker); - docker_envvars(&mut docker, config, target, msg_info)?; + docker_envvars( + &mut docker, + config, + target, + cargo_variant.uses_zig(), + msg_info, + )?; let mount_volumes = docker_mount( &mut docker, @@ -83,10 +89,18 @@ pub(crate) fn run( docker.arg("-t"); } } - let mut image = image_name(config, target)?; + let mut image = image_name(config, target, cargo_variant.uses_zig())?; if needs_custom_image(target, config) { - image = custom_image_build(target, config, metadata, dirs, engine, msg_info) - .wrap_err("when building custom image")? + image = custom_image_build( + target, + config, + metadata, + dirs, + engine, + msg_info, + cargo_variant.uses_zig(), + ) + .wrap_err("when building custom image")? } docker diff --git a/src/docker/mod.rs b/src/docker/mod.rs index f2c9da22e..17342a56a 100644 --- a/src/docker/mod.rs +++ b/src/docker/mod.rs @@ -13,7 +13,7 @@ use std::process::ExitStatus; use crate::cargo::CargoMetadata; use crate::errors::*; use crate::shell::MessageInfo; -use crate::{Config, Target}; +use crate::{CargoVariant, Config, Target}; #[allow(clippy::too_many_arguments)] // TODO: refactor pub fn run( @@ -22,7 +22,7 @@ pub fn run( args: &[String], metadata: &CargoMetadata, config: &Config, - uses_xargo: bool, + cargo_variant: CargoVariant, sysroot: &Path, msg_info: MessageInfo, docker_in_docker: bool, @@ -35,7 +35,7 @@ pub fn run( args, metadata, config, - uses_xargo, + cargo_variant, sysroot, msg_info, docker_in_docker, @@ -49,7 +49,7 @@ pub fn run( args, metadata, config, - uses_xargo, + cargo_variant, sysroot, msg_info, docker_in_docker, diff --git a/src/docker/remote.rs b/src/docker/remote.rs index 46a8b2597..d684e7b3a 100644 --- a/src/docker/remote.rs +++ b/src/docker/remote.rs @@ -17,7 +17,7 @@ use crate::rustc::{self, VersionMetaExt}; use crate::rustup; use crate::shell::{self, MessageInfo, Stream}; use crate::temp; -use crate::{Host, Target}; +use crate::{CargoVariant, Host, Target}; // the mount directory for the data volume. pub const MOUNT_PREFIX: &str = "/cross"; @@ -758,7 +758,7 @@ pub(crate) fn run( args: &[String], metadata: &CargoMetadata, config: &Config, - uses_xargo: bool, + cargo_variant: CargoVariant, sysroot: &Path, msg_info: MessageInfo, docker_in_docker: bool, @@ -818,7 +818,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, config, target, msg_info)?; + docker_envvars( + &mut docker, + config, + target, + cargo_variant.uses_zig(), + msg_info, + )?; let mut volumes = vec![]; let mount_volumes = docker_mount( @@ -851,7 +857,7 @@ pub(crate) fn run( } docker - .arg(&image_name(config, target)?) + .arg(&image_name(config, target, 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)?; @@ -1000,7 +1006,7 @@ pub(crate) fn run( final_args.push("--target-dir".to_string()); final_args.push(target_dir_string); } - let mut cmd = cargo_safe_command(uses_xargo); + let mut cmd = cargo_safe_command(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 fb3f6b31c..b029d3958 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::{self, MessageInfo, Verbosity}; -use crate::Target; +use crate::{CargoVariant, Target}; pub use super::custom::CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX; @@ -212,12 +212,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) { @@ -268,6 +264,7 @@ pub(crate) fn docker_envvars( docker: &mut Command, config: &Config, target: &Target, + uses_zig: bool, msg_info: MessageInfo, ) -> Result<()> { for ref var in config.env_passthrough(target)?.unwrap_or_default() { @@ -286,6 +283,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().unwrap() { @@ -478,8 +479,9 @@ pub(crate) fn custom_image_build( Directories { host_root, .. }: Directories, engine: &Engine, msg_info: MessageInfo, + uses_zig: bool, ) -> Result { - let mut image = image_name(config, target)?; + let mut image = image_name(config, target, uses_zig)?; if let Some(path) = config.dockerfile(target)? { let context = config.dockerfile_context(target)?; @@ -500,6 +502,7 @@ pub(crate) fn custom_image_build( config.dockerfile_build_args(target)?.unwrap_or_default(), target, msg_info, + uses_zig, ) .wrap_err("when building dockerfile")?; } @@ -525,6 +528,7 @@ pub(crate) fn custom_image_build( Some(("CROSS_CMD", pre_build.join("\n"))), target, msg_info, + uses_zig, ) .wrap_err("when pre-building") .with_note(|| format!("CROSS_CMD={}", pre_build.join("\n")))?; @@ -534,7 +538,7 @@ pub(crate) fn custom_image_build( Ok(image) } -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); } @@ -552,7 +556,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 038d674cf..21152c0b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -317,11 +317,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) } } } @@ -353,11 +353,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 + } } pub fn run() -> Result { @@ -387,7 +418,9 @@ pub fn run() -> Result { .unwrap_or_else(|| Target::from(host.triple(), &target_list)); config.confusable_target(&target, args.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) => { shell::warn(err, args.msg_info)?; @@ -420,6 +453,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 std::env::var("CROSS_CUSTOM_TOOLCHAIN").is_err() { // 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. @@ -475,7 +509,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_string()); - args_with_target.push(target.triple().to_string()); + let mut target_and_libc = target.triple().to_string(); + 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() @@ -513,7 +552,7 @@ pub fn run() -> Result { &filtered_args, &metadata, &config, - uses_xargo, + cargo_variant, &sysroot, args.msg_info, args.docker_in_docker, diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index d32b2c02f..5bfdb72d7 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -69,6 +69,11 @@ pub fn ci(args: CiJob, metadata: CargoMetadata) -> cross::Result<()> { if target.has_ci_image() { gha_output("has-image", "true") } + if target.is_default_test_image() { + gha_output("test-variant", "default") + } else { + gha_output("test-variant", &target.triplet) + } } CiJob::Check { ref_type, ref_name } => { let version = semver::Version::parse(&cross_meta.version)?; diff --git a/xtask/src/util.rs b/xtask/src/util.rs index 8eafeb3a0..f70f43618 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -129,6 +129,11 @@ impl ImageTarget { .iter() .any(|m| m.builds_image() && m.target == self.triplet && m.sub == self.sub) } + + /// Determine if this target uses the default test script + pub fn is_default_test_image(&self) -> bool { + self.triplet != "zig" + } } impl std::str::FromStr for ImageTarget {