mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 22:30:03 +00:00
Fix detect-targets
on Linux and add CI testing (#1344)
* Testing: Add `detect-targets/src/main.rs` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `detect-targets` linux: `guess_host_triple` could return wrong libc info so it has to check it manually instead of simply providing alternatives like other OSes. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `get_ld_flavor` for Alpine's gcompat glibc Its output is different from regular glibc, so we need to hardcode that particular cases. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix detection of alpine specific musl target Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add ci testing for Alpine Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add CI test for detect-targets on ubuntu Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor `get_ld_flavor` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix shellcheck Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more CI test for ubuntu and fixed typo in it Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm distro specific target as it breaks `cargo-install` fallback Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Make sure all binaries are built in CI Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `package.metadata.binstall` for `detect-targets` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix justfile Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `detect-targets-{alpine, ubuntu}-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `detect-targets-ubuntu-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `debug-targets-ubuntu-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * `set -exuo pipefail` in `detect-targets-ubuntu-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Simplify `detect-targets-*-test`: Use `Swatinem/rust-cache@v2` directly instead of using `just-setup` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm dup steps in `detect-targets-ubuntu-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `ls` to detect-targets-alpine-test for debugging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * FIx `detect-targets-alpine-test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `get_ld_flavor` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `linux::detect_targets` on ubuntu & glibc system Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * FIx `linux::detect_targets` glibc checking Check dynlib suffix Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> --------- Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
d657fbe518
commit
76a692224d
7 changed files with 159 additions and 85 deletions
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
|
@ -131,6 +131,39 @@ jobs:
|
||||||
CARGO_PROFILE_RELEASE_LTO: no
|
CARGO_PROFILE_RELEASE_LTO: no
|
||||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 4
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 4
|
||||||
|
|
||||||
|
detect-targets-alpine-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
|
||||||
|
TARGET: x86_64-unknown-linux-musl
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install x86_64-unknown-linux-musl target
|
||||||
|
run: rustup target add $TARGET
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Build detect-targets
|
||||||
|
run: |
|
||||||
|
pip3 install -r zigbuild-requirements.txt
|
||||||
|
cd crates/detect-targets && cargo zigbuild --target $TARGET
|
||||||
|
- name: Run test in alpine
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
--mount src="$PWD/target/$TARGET/debug/detect-targets",dst=/usr/local/bin/detect-targets,type=bind \
|
||||||
|
--mount src="$PWD/test-detect-targets-musl.sh",dst=/usr/local/bin/test.sh,type=bind \
|
||||||
|
alpine test.sh "$TARGET"
|
||||||
|
|
||||||
|
detect-targets-ubuntu-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Build detect-targets
|
||||||
|
run: cargo build --bin detect-targets
|
||||||
|
- name: Run test in ubuntu
|
||||||
|
run: |
|
||||||
|
set -exuo pipefail
|
||||||
|
[ "$(./target/debug/detect-targets)" = "$(printf '%s\n%s' x86_64-unknown-linux-gnu x86_64-unknown-linux-musl)" ]
|
||||||
|
|
||||||
# Dummy job to have a stable name for the "all tests pass" requirement
|
# Dummy job to have a stable name for the "all tests pass" requirement
|
||||||
tests-pass:
|
tests-pass:
|
||||||
name: Tests pass
|
name: Tests pass
|
||||||
|
@ -139,6 +172,8 @@ jobs:
|
||||||
- cross-check
|
- cross-check
|
||||||
- lint
|
- lint
|
||||||
- release-builds
|
- release-builds
|
||||||
|
- detect-targets-alpine-test
|
||||||
|
- detect-targets-ubuntu-test
|
||||||
if: always() # always run even if dependencies fail
|
if: always() # always run even if dependencies fail
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -20,3 +20,6 @@ windows-dll = { version = "0.4.1", features = ["windows"], default-features = fa
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.28.2", features = ["macros"], default-features = false }
|
tokio = { version = "1.28.2", features = ["macros"], default-features = false }
|
||||||
|
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/cargo-binstall-{ target }.full.{ archive-format }"
|
||||||
|
|
|
@ -38,19 +38,21 @@ pub async fn detect_targets() -> Vec<String> {
|
||||||
.to_string()
|
.to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut targets = vec![target];
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(target_os = "macos")] {
|
if #[cfg(target_os = "macos")] {
|
||||||
|
let mut targets = vec![target];
|
||||||
targets.extend(macos::detect_alternative_targets(&targets[0]).await);
|
targets.extend(macos::detect_alternative_targets(&targets[0]).await);
|
||||||
|
targets
|
||||||
} else if #[cfg(target_os = "windows")] {
|
} else if #[cfg(target_os = "windows")] {
|
||||||
|
let mut targets = vec![target];
|
||||||
targets.extend(windows::detect_alternative_targets(&targets[0]));
|
targets.extend(windows::detect_alternative_targets(&targets[0]));
|
||||||
|
targets
|
||||||
} else if #[cfg(target_os = "linux")] {
|
} else if #[cfg(target_os = "linux")] {
|
||||||
targets.extend(linux::detect_alternative_targets(&targets[0]).await);
|
// Linux is a bit special, since the result from `guess_host_triple`
|
||||||
|
// might be wrong about whether glibc or musl is used.
|
||||||
|
linux::detect_targets(target).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Figure out what the host target is using `rustc`.
|
/// Figure out what the host target is using `rustc`.
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
path::Path,
|
|
||||||
process::{Output, Stdio},
|
process::{Output, Stdio},
|
||||||
|
str,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::{process::Command, task};
|
use tokio::{process::Command, task};
|
||||||
|
|
||||||
pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator<Item = String> {
|
pub(super) async fn detect_targets(target: String) -> Vec<String> {
|
||||||
let (prefix, postfix) = target
|
let (prefix, postfix) = target
|
||||||
.rsplit_once('-')
|
.rsplit_once('-')
|
||||||
.expect("unwrap: target always has a -");
|
.expect("unwrap: target always has a -");
|
||||||
|
@ -37,71 +36,81 @@ pub(super) async fn detect_alternative_targets(target: &str) -> impl Iterator<It
|
||||||
.expect("unwrap: target always has a - for cpu_arch")
|
.expect("unwrap: target always has a - for cpu_arch")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let has_glibc = task::spawn({
|
let cpu_arch_suffix = cpu_arch.replace('_', "-");
|
||||||
let glibc_path = format!("/lib/ld-linux-{cpu_arch}.so.1");
|
|
||||||
async move { is_gnu_ld(&glibc_path).await }
|
|
||||||
});
|
|
||||||
|
|
||||||
let distro_if_has_non_std_glibc = task::spawn(async {
|
let handles: Vec<_> = [
|
||||||
if is_gnu_ld("/usr/bin/ldd").await {
|
format!("/lib/ld-linux-{cpu_arch_suffix}.so.2"),
|
||||||
get_distro_name().await
|
format!("/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
|
||||||
} else {
|
format!("/usr/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
|
||||||
None
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| AutoAbortHandle(tokio::spawn(is_gnu_ld(p))))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let has_glibc = async move {
|
||||||
|
for mut handle in handles {
|
||||||
|
if let Ok(true) = (&mut handle.0).await {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let distro_if_has_musl_dynlib = if get_ld_flavor(&format!(
|
false
|
||||||
"/lib/ld-musl-{cpu_arch}.so.1"
|
}
|
||||||
))
|
.await;
|
||||||
.await
|
|
||||||
== Some(Libc::Musl)
|
|
||||||
{
|
|
||||||
get_distro_name().await
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
[
|
[
|
||||||
has_glibc
|
has_glibc.then(|| format!("{cpu_arch}-unknown-linux-gnu{abi}")),
|
||||||
.await
|
|
||||||
.unwrap_or(false)
|
|
||||||
.then(|| format!("{cpu_arch}-unknown-linux-gnu{abi}")),
|
|
||||||
distro_if_has_non_std_glibc
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|distro_name| format!("{cpu_arch}-{distro_name}-linux-gnu{abi}")),
|
|
||||||
// Fallback for Linux flavors like Alpine, which has a musl dyn libc
|
|
||||||
distro_if_has_musl_dynlib
|
|
||||||
.map(|distro_name| format!("{cpu_arch}-{distro_name}-linux-musl{abi}")),
|
|
||||||
Some(musl_fallback_target()),
|
Some(musl_fallback_target()),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Libc::Android | Libc::Unknown => [
|
Libc::Android | Libc::Unknown => [Some(target.clone()), Some(musl_fallback_target())],
|
||||||
Some(target.to_string()),
|
|
||||||
Some(musl_fallback_target()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_gnu_ld(cmd: &str) -> bool {
|
async fn is_gnu_ld(cmd: String) -> bool {
|
||||||
get_ld_flavor(cmd).await == Some(Libc::Gnu)
|
get_ld_flavor(&cmd).await == Some(Libc::Gnu)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_ld_flavor(cmd: &str) -> Option<Libc> {
|
async fn get_ld_flavor(cmd: &str) -> Option<Libc> {
|
||||||
Command::new(cmd)
|
let Output {
|
||||||
|
status,
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
} = Command::new(cmd)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()?;
|
||||||
.and_then(|Output { stdout, stderr, .. }| {
|
|
||||||
Libc::parse(&stdout).or_else(|| Libc::parse(&stderr))
|
const ALPINE_GCOMPAT: &str = r#"This is the gcompat ELF interpreter stub.
|
||||||
})
|
You are not meant to run this directly.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
// Executing glibc ldd or /lib/ld-linux-{cpu_arch}.so.1 will always
|
||||||
|
// succeeds.
|
||||||
|
String::from_utf8_lossy(&stdout)
|
||||||
|
.contains("GLIBC")
|
||||||
|
.then_some(Libc::Gnu)
|
||||||
|
} else if status.code() == Some(1) {
|
||||||
|
// On Alpine, executing both the gcompat glibc and the ldd and
|
||||||
|
// /lib/ld-musl-{cpu_arch}.so.1 will fail with exit status 1.
|
||||||
|
if str::from_utf8(&stdout).as_deref() == Ok(ALPINE_GCOMPAT) {
|
||||||
|
// Alpine's gcompat package will output ALPINE_GCOMPAT to stdout
|
||||||
|
Some(Libc::Gnu)
|
||||||
|
} else if String::from_utf8_lossy(&stderr).contains("musl libc") {
|
||||||
|
// Alpine/s ldd and musl dynlib will output to stderr
|
||||||
|
Some(Libc::Musl)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
|
@ -112,37 +121,10 @@ enum Libc {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Libc {
|
struct AutoAbortHandle<T>(task::JoinHandle<T>);
|
||||||
fn parse(output: &[u8]) -> Option<Self> {
|
|
||||||
let s = String::from_utf8_lossy(output);
|
impl<T> Drop for AutoAbortHandle<T> {
|
||||||
if s.contains("musl libc") {
|
fn drop(&mut self) {
|
||||||
Some(Self::Musl)
|
self.0.abort();
|
||||||
} else if s.contains("GLIBC") {
|
|
||||||
Some(Self::Gnu)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_distro_name() -> Option<String> {
|
|
||||||
task::spawn_blocking(get_distro_name_blocking)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_distro_name_blocking() -> Option<String> {
|
|
||||||
match fs::read_to_string("/etc/os-release") {
|
|
||||||
Ok(os_release) => os_release
|
|
||||||
.lines()
|
|
||||||
.find_map(|line| line.strip_prefix("ID=\"")?.strip_suffix('"'))
|
|
||||||
.map(ToString::to_string),
|
|
||||||
Err(_) => (Path::new("/etc/nix/nix.conf").is_file()
|
|
||||||
&& ["/nix/store", "/nix/var/profiles"]
|
|
||||||
.into_iter()
|
|
||||||
.map(Path::new)
|
|
||||||
.all(Path::is_dir))
|
|
||||||
.then_some("nixos".to_string()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
crates/detect-targets/src/main.rs
Normal file
17
crates/detect-targets/src/main.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use detect_targets::detect_targets;
|
||||||
|
use tokio::runtime;
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let targets = runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()?
|
||||||
|
.block_on(detect_targets());
|
||||||
|
|
||||||
|
for target in targets {
|
||||||
|
println!("{target}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
15
justfile
15
justfile
|
@ -297,6 +297,9 @@ package-prepare: build package-dir
|
||||||
just get-output detect-wasi{{output-ext}} packages/prep
|
just get-output detect-wasi{{output-ext}} packages/prep
|
||||||
-just get-output detect-wasi.dSYM packages/prep
|
-just get-output detect-wasi.dSYM packages/prep
|
||||||
|
|
||||||
|
just get-output detect-targets{{output-ext}} packages/prep
|
||||||
|
-just get-output detect-targets.dSYM packages/prep
|
||||||
|
|
||||||
# when https://github.com/rust-lang/cargo/pull/11384 lands, we can use
|
# when https://github.com/rust-lang/cargo/pull/11384 lands, we can use
|
||||||
# -just get-output cargo_binstall.dwp packages/prep
|
# -just get-output cargo_binstall.dwp packages/prep
|
||||||
# underscored dwp name needs to remain for debuggers to find the file properly
|
# underscored dwp name needs to remain for debuggers to find the file properly
|
||||||
|
@ -308,6 +311,9 @@ package-prepare: build package-dir
|
||||||
just get-output detect-wasi packages/prep
|
just get-output detect-wasi packages/prep
|
||||||
-cp {{output-folder}}/deps/detect_wasi-*.dwp packages/prep/detect_wasi.dwp
|
-cp {{output-folder}}/deps/detect_wasi-*.dwp packages/prep/detect_wasi.dwp
|
||||||
|
|
||||||
|
just get-output detect-targets packages/prep
|
||||||
|
-cp {{output-folder}}/deps/detect_target-*.dwp packages/prep/detect_target.dwp
|
||||||
|
|
||||||
# underscored pdb name needs to remain for debuggers to find the file properly
|
# underscored pdb name needs to remain for debuggers to find the file properly
|
||||||
# read from deps because sometimes cargo doesn't copy the pdb to the output folder
|
# read from deps because sometimes cargo doesn't copy the pdb to the output folder
|
||||||
[windows]
|
[windows]
|
||||||
|
@ -318,6 +324,9 @@ package-prepare: build package-dir
|
||||||
just get-output detect-wasi.exe packages/prep
|
just get-output detect-wasi.exe packages/prep
|
||||||
-just get-output deps/detect_wasi.pdb packages/prep
|
-just get-output deps/detect_wasi.pdb packages/prep
|
||||||
|
|
||||||
|
just get-output detect-targets.exe packages/prep
|
||||||
|
-just get-output deps/detect_target.pdb packages/prep
|
||||||
|
|
||||||
# we don't get dSYM bundles for universal binaries; unsure if it's even a thing
|
# we don't get dSYM bundles for universal binaries; unsure if it's even a thing
|
||||||
[macos]
|
[macos]
|
||||||
lipo-prepare: package-dir
|
lipo-prepare: package-dir
|
||||||
|
@ -335,6 +344,11 @@ lipo-prepare: package-dir
|
||||||
just target=x86_64h-apple-darwin get-output detect-wasi{{output-ext}} packages/prep/x64h
|
just target=x86_64h-apple-darwin get-output detect-wasi{{output-ext}} packages/prep/x64h
|
||||||
lipo -create -output packages/prep/detect-wasi{{output-ext}} packages/prep/{arm64,x64,x64h}/detect-wasi{{output-ext}}
|
lipo -create -output packages/prep/detect-wasi{{output-ext}} packages/prep/{arm64,x64,x64h}/detect-wasi{{output-ext}}
|
||||||
|
|
||||||
|
just target=aarch64-apple-darwin get-output detect-targets{{output-ext}} packages/prep/arm64
|
||||||
|
just target=x86_64-apple-darwin get-output detect-targets{{output-ext}} packages/prep/x64
|
||||||
|
just target=x86_64h-apple-darwin get-output detect-targets{{output-ext}} packages/prep/x64h
|
||||||
|
lipo -create -output packages/prep/detect-targets{{output-ext}} packages/prep/{arm64,x64,x64h}/detect-targets{{output-ext}}
|
||||||
|
|
||||||
rm -rf packages/prep/{arm64,x64,x64h}
|
rm -rf packages/prep/{arm64,x64,x64h}
|
||||||
|
|
||||||
|
|
||||||
|
@ -370,6 +384,7 @@ repackage-lipo: package-dir
|
||||||
|
|
||||||
lipo -create -output packages/prep/{{output-filename}} packages/prep/{arm64,x64,x64h}/{{output-filename}}
|
lipo -create -output packages/prep/{{output-filename}} packages/prep/{arm64,x64,x64h}/{{output-filename}}
|
||||||
lipo -create -output packages/prep/detect-wasi packages/prep/{arm64,x64,x64h}/detect-wasi
|
lipo -create -output packages/prep/detect-wasi packages/prep/{arm64,x64,x64h}/detect-wasi
|
||||||
|
lipo -create -output packages/prep/detect-targets packages/prep/{arm64,x64,x64h}/detect-targets
|
||||||
|
|
||||||
./packages/prep/{{output-filename}} -vV
|
./packages/prep/{{output-filename}} -vV
|
||||||
|
|
||||||
|
|
20
test-detect-targets-musl.sh
Executable file
20
test-detect-targets-musl.sh
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/ash
|
||||||
|
|
||||||
|
# shellcheck shell=dash
|
||||||
|
|
||||||
|
set -exuo pipefail
|
||||||
|
|
||||||
|
TARGET=${1?}
|
||||||
|
|
||||||
|
[ "$(detect-targets)" = "$TARGET" ]
|
||||||
|
|
||||||
|
apk update
|
||||||
|
apk add gcompat
|
||||||
|
|
||||||
|
ls -lsha /lib
|
||||||
|
|
||||||
|
GNU_TARGET=$(echo "$TARGET" | sed 's/musl/gnu/')
|
||||||
|
|
||||||
|
[ "$(detect-targets)" = "$(printf '%s\n%s' "$GNU_TARGET" "$TARGET")" ]
|
||||||
|
|
||||||
|
echo
|
Loading…
Add table
Add a link
Reference in a new issue