diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b521928dd..80e89bf63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,8 +321,69 @@ jobs: LATEST: ${{ needs.check.outputs.is-latest || 'false' }} shell: bash + # we should always have an artifact from a previous build. + remote: + needs: [shellcheck, test, check] + runs-on: ubuntu-latest + if: github.actor == 'bors[bot]' + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-rust + + - name: LLVM instrument coverage + id: remote-cov + uses: ./.github/actions/cargo-llvm-cov + with: + name: integration-remote + + - name: Run Remote Test + env: + TARGET: aarch64-unknown-linux-gnu + run: ./ci/test-remote.sh + shell: bash + + bisect: + needs: [shellcheck, test, check] + runs-on: ubuntu-latest + if: github.actor == 'bors[bot]' + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-rust + + - name: LLVM instrument coverage + id: bisect-cov + uses: ./.github/actions/cargo-llvm-cov + with: + name: integration-bisect + + - name: Run Bisect Test + env: + TARGET: aarch64-unknown-linux-gnu + run: ./ci/test-bisect.sh + shell: bash + + docker-in-docker: + needs: [shellcheck, test, check] + runs-on: ubuntu-latest + if: github.actor == 'bors[bot]' + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-rust + + - name: LLVM instrument coverage + id: docker-in-docker-cov + uses: ./.github/actions/cargo-llvm-cov + with: + name: integration-docker-in-docker + + - name: Run Docker-in-Docker Test + env: + TARGET: aarch64-unknown-linux-gnu + run: ./ci/test-docker-in-docker.sh + shell: bash + publish: - needs: [build, check] + needs: [build, check, fmt, clippy, cargo-deny, remote, bisect, docker-in-docker] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/ci/shared.sh b/ci/shared.sh new file mode 100755 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-bisect.sh b/ci/test-bisect.sh new file mode 100755 index 000000000..4db9a5acf --- /dev/null +++ b/ci/test-bisect.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091,SC1090 + +# test to see that custom toolchains work + +set -x +set -eo pipefail + +if [[ -z "${TARGET}" ]]; then + export TARGET="aarch64-unknown-linux-gnu" +fi + +ci_dir=$(dirname "${BASH_SOURCE[0]}") +ci_dir=$(realpath "${ci_dir}") +. "${ci_dir}"/shared.sh +project_home=$(dirname "${ci_dir}") + +main() { + local td= + local err= + + retry cargo fetch + cargo build + cargo install cargo-bisect-rustc --debug + 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}" + retry cargo fetch + # shellcheck disable=SC2016 + echo '#!/usr/bin/env bash +export CROSS_CUSTOM_TOOLCHAIN=1 +exec "${CROSS}" run --target '"${TARGET}" > bisect.sh + chmod +x bisect.sh + + if ! err=$(cargo bisect-rustc --script=./bisect.sh --target "${TARGET}" 2>&1 >/dev/null); then + if [[ "${err}" != *"does not reproduce the regression"* ]]; then + echo "${err}" + exit 1 + fi + else + echo "should have failed, instead succeeded" 1>&2 + exit 1 + fi + popd + + rm -rf "${td}" +} + +main diff --git a/ci/test-docker-in-docker.sh b/ci/test-docker-in-docker.sh new file mode 100755 index 000000000..3057c68d8 --- /dev/null +++ b/ci/test-docker-in-docker.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1004 + +# test to see that running docker-in-docker works + +set -x +set -eo pipefail + +if [[ -z "${TARGET}" ]]; then + export TARGET="aarch64-unknown-linux-gnu" +fi + +source=$(dirname "${BASH_SOURCE[0]}") +source=$(realpath "${source}") +home=$(dirname "${source}") + +main() { + docker run -v "${home}":"${home}" -w "${home}" \ + --rm -e TARGET -e RUSTFLAGS -e RUST_TEST_THREADS \ + -e LLVM_PROFILE_FILE -e CARGO_INCREMENTAL \ + -v /var/run/docker.sock:/var/run/docker.sock \ + docker:18.09-dind sh -c ' +#!/usr/bin/env sh +set -x +set -euo pipefail + +apk add curl +curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "${HOME}/.cargo/env" + +# building on release is slow +apk add libgcc gcc musl-dev +cargo test --workspace +cargo install --path . --force --debug + +export CROSS_CONTAINER_IN_CONTAINER=1 + +apk add git +td="$(mktemp -d)" +git clone --depth 1 https://github.com/cross-rs/rust-cpp-hello-word "${td}" +cd "${td}" +cross run --target "${TARGET}" --verbose + +td="$(mktemp -d)" +git clone --depth 1 https://github.com/cross-rs/test-workspace "${td}" +cd "${td}" +cross build --target "${TARGET}" --workspace \ + --manifest-path="./workspace/Cargo.toml" --verbose +cd workspace +cross build --target "${TARGET}" --workspace --verbose +cd binary +cross run --target "${TARGET}" --verbose +' +} + +main diff --git a/ci/test-remote.sh b/ci/test-remote.sh new file mode 100755 index 000000000..8fb3583ed --- /dev/null +++ b/ci/test-remote.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091,SC1090 + +# test to see that remote docker support works. + +set -x +set -eo pipefail + +export CROSS_REMOTE=1 +if [[ -z "${TARGET}" ]]; then + export TARGET="aarch64-unknown-linux-gnu" +fi + +ci_dir=$(dirname "${BASH_SOURCE[0]}") +ci_dir=$(realpath "${ci_dir}") +. "${ci_dir}"/shared.sh +project_home=$(dirname "${ci_dir}") + +main() { + local err= + + retry cargo fetch + cargo build + export CROSS="${project_home}/target/debug/cross" + export CROSS_UTIL="${project_home}/target/debug/cross-util" + + # if the create volume fails, ensure it exists. + if ! err=$("${CROSS_UTIL}" volumes create 2>&1 >/dev/null); then + if [[ "${err}" != *"already exists"* ]]; then + echo "${err}" + exit 1 + fi + fi + cross_test_cpp + "${CROSS_UTIL}" volumes remove + + # ensure the data volume was removed. + cross_test_cpp +} + +cross_test_cpp() { + local td= + td="$(mktemp -d)" + + git clone --depth 1 https://github.com/cross-rs/rust-cpp-hello-word "${td}" + + pushd "${td}" + retry cargo fetch + "${CROSS}" run --target "${TARGET}" + 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/src/bin/commands/containers.rs b/src/bin/commands/containers.rs index ffa3e863a..a6a3712b5 100644 --- a/src/bin/commands/containers.rs +++ b/src/bin/commands/containers.rs @@ -482,6 +482,7 @@ pub fn remove_persistent_volume( engine: &docker::Engine, channel: Option<&str>, ) -> cross::Result<()> { + // we only need a triple that needs docker: the actual target doesn't matter. let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let triple = cross::Host::X86_64UnknownLinuxGnu.triple(); let (_, _, dirs) = diff --git a/src/docker/shared.rs b/src/docker/shared.rs index 36893b2af..0465ee454 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -763,6 +763,162 @@ mod tests { } } + mod directories { + use super::*; + use crate::cargo::cargo_metadata_with_args; + use crate::temp; + + fn unset_env() -> Vec<(&'static str, Option)> { + let mut result = vec![]; + let envvars = ["CARGO_HOME", "XARGO_HOME", "NIX_STORE"]; + for var in envvars { + result.push((var, env::var(var).ok())); + env::remove_var(var); + } + + result + } + + fn reset_env(vars: Vec<(&'static str, Option)>) { + for (var, value) in vars { + if let Some(value) = value { + env::set_var(var, value); + } + } + } + + fn create_engine(msg_info: MessageInfo) -> Result { + Engine::from_path(get_container_engine()?, Some(false), msg_info) + } + + fn cargo_metadata(subdir: bool, msg_info: MessageInfo) -> Result { + let mut metadata = cargo_metadata_with_args( + Some(Path::new(env!("CARGO_MANIFEST_DIR"))), + None, + msg_info, + )? + .ok_or_else(|| eyre::eyre!("could not find cross workspace"))?; + + let root = match subdir { + true => get_cwd()?.join("member"), + false => get_cwd()?.parent().unwrap().to_path_buf(), + }; + fs::create_dir_all(&root)?; + metadata.workspace_root = root; + metadata.target_directory = metadata.workspace_root.join("target"); + + Ok(metadata) + } + + fn home() -> Result { + home::home_dir().ok_or_else(|| eyre::eyre!("could not find home directory")) + } + + fn get_cwd() -> Result { + // we need this directory to exist for Windows + let path = temp::dir()?.join("Documents").join("package"); + fs::create_dir_all(&path)?; + Ok(path) + } + + fn get_sysroot() -> Result { + Ok(home()? + .join(".rustup") + .join("toolchains") + .join("stable-x86_64-unknown-linux-gnu")) + } + + fn get_directories( + metadata: &CargoMetadata, + mount_finder: &MountFinder, + ) -> Result { + let cwd = get_cwd()?; + let sysroot = get_sysroot()?; + Directories::create(mount_finder, metadata, &cwd, &sysroot) + } + + fn path_to_posix(path: &Path) -> Result { + #[cfg(target_os = "windows")] + { + path.as_wslpath() + } + #[cfg(not(target_os = "windows"))] + { + path.as_posix() + } + } + + #[track_caller] + fn paths_equal(x: &Path, y: &Path) -> Result<()> { + assert_eq!(path_to_posix(x)?, path_to_posix(y)?); + Ok(()) + } + + #[test] + fn test_host() -> Result<()> { + let vars = unset_env(); + let mount_finder = MountFinder::new(vec![]); + let metadata = cargo_metadata(false, MessageInfo::default())?; + let directories = get_directories(&metadata, &mount_finder)?; + paths_equal(&directories.cargo, &home()?.join(".cargo"))?; + paths_equal(&directories.xargo, &home()?.join(".xargo"))?; + paths_equal(&directories.host_root, &metadata.workspace_root)?; + assert_eq!( + &directories.mount_root, + &path_to_posix(&metadata.workspace_root)? + ); + assert_eq!(&directories.mount_cwd, &path_to_posix(&get_cwd()?)?); + + reset_env(vars); + Ok(()) + } + + #[test] + #[cfg_attr(not(target_os = "linux"), ignore)] + fn test_docker_in_docker() -> Result<()> { + let vars = unset_env(); + + let engine = create_engine(MessageInfo::default()); + let hostname = env::var("HOSTNAME"); + if engine.is_err() || hostname.is_err() { + eprintln!("could not get container engine or no hostname found"); + reset_env(vars); + return Ok(()); + } + let engine = engine.unwrap(); + let hostname = hostname.unwrap(); + let output = subcommand(&engine, "inspect") + .arg(hostname) + .run_and_get_output(MessageInfo::default())?; + if !output.status.success() { + eprintln!("inspect failed"); + reset_env(vars); + return Ok(()); + } + + let mount_finder = MountFinder::create(&engine, true)?; + let metadata = cargo_metadata(true, MessageInfo::default())?; + let directories = get_directories(&metadata, &mount_finder)?; + let mount_finder = MountFinder::new(docker_read_mount_paths(&engine)?); + let mount_path = |p| mount_finder.find_mount_path(p); + + paths_equal(&directories.cargo, &mount_path(home()?.join(".cargo")))?; + paths_equal(&directories.xargo, &mount_path(home()?.join(".xargo")))?; + paths_equal(&directories.host_root, &mount_path(get_cwd()?))?; + assert_eq!( + &directories.mount_root, + &path_to_posix(&mount_path(get_cwd()?))? + ); + assert_eq!( + &directories.mount_cwd, + &path_to_posix(&mount_path(get_cwd()?))? + ); + + reset_env(vars); + Ok(()) + } + } + mod mount_finder { use super::*;