Skip to content

Add plain cross-compilation mode for riscv64 and loongarch64 #1804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/ci/customizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ dist will transparently use either of:

* [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild)
* [cargo-xwin](https://github.com/rust-cross/cargo-xwin)
* plain `cargo build` with target-specific linkers

To try and build for the target you specified, from the host you specified.

Expand Down
7 changes: 7 additions & 0 deletions cargo-dist/src/backend/ci/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use tracing::warn;

use crate::{
backend::{diff_files, templates::TEMPLATE_CI_GITHUB},
build::cargo::target_gcc_packages,
build_wrapper_for_cross,
config::{
v1::{ci::github::GithubCiConfig, publishers::PublisherConfig},
Expand Down Expand Up @@ -336,6 +337,12 @@ impl GithubCiInfo {

let mut dist_args = String::from("--artifacts=local");
for target in &targets {
let target_triple = target.parse()?;
if let Some(CargoBuildWrapper::Plain) =
build_wrapper_for_cross(&real_triple, &target_triple)?
{
dependencies.append(&mut target_gcc_packages(&real_triple, &target_triple)?);
}
write!(dist_args, " --target={target}").unwrap();
}
let packages_install = system_deps_install_script(&runner, &targets, &dependencies)?;
Expand Down
106 changes: 104 additions & 2 deletions cargo-dist/src/build/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ use std::env;

use axoprocess::Cmd;
use axoproject::WorkspaceIdx;
use dist_schema::target_lexicon::{Architecture, Environment, Triple};
use dist_schema::{DistManifest, TripleName};
use dist_schema::target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
use dist_schema::{AptPackageName, DistManifest, TripleName};
use miette::{Context, IntoDiagnostic};
use tracing::warn;

use crate::build::BuildExpectations;
use crate::config::{
DependencyKind, SystemDependencies, SystemDependency, SystemDependencyComplex,
};
use crate::env::{calculate_ldflags, fetch_brew_env, parse_env, select_brew_env};
use crate::{
build_wrapper_for_cross, errors::*, BinaryIdx, BuildStep, CargoBuildWrapper, DistGraphBuilder,
Expand Down Expand Up @@ -162,6 +165,95 @@ impl<'a> DistGraphBuilder<'a> {
}
}

/// Get the apt packages for target apt linker from `host` platform.
pub fn target_gcc_packages(host: &Triple, target: &Triple) -> DistResult<SystemDependencies> {
if target.operating_system != OperatingSystem::Linux {
return Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: "cargo-build is not a supported cross-compilation method for this combination"
.to_string(),
});
}
let mut deps = SystemDependencies::default();
let _env = match target.environment {
Environment::Gnu => "gnu",
Environment::Musl => {
// TODO: Currently we do not install musl cross toolchain
// because of missing apt packages. The user is up to
// install musl cross toolchain themselves.
return Ok(deps);
}
_ => {
return Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: format!(
"cargo-build is not a supported cross-compilation method for {}",
target.environment
),
});
}
};
let linux_arch = match target.architecture {
// Strip ISA exts part from riscv triple. e.g. riscv64gc -> riscv64
Architecture::Riscv64(_) => "riscv64".into(),
Architecture::Riscv32(_) => "riscv32".into(),
arch => arch.into_str(),
};
deps.apt.insert(
AptPackageName::new(format!("binutils-{linux_arch}-linux-gnu")),
SystemDependency(SystemDependencyComplex {
version: None,
stage: vec![DependencyKind::Build],
targets: vec![TripleName::new(target.to_string())],
}),
);
deps.apt.insert(
AptPackageName::new(format!("gcc-{linux_arch}-linux-gnu")),
SystemDependency(SystemDependencyComplex {
version: None,
stage: vec![DependencyKind::Build],
targets: vec![TripleName::new(target.to_string())],
}),
);
// TODO: support homebrew
Ok(deps)
}

/// Get the `target` specific gcc compiler to use as linker from `host` platform.
pub fn target_gcc_linker(host: &Triple, target: &Triple) -> DistResult<String> {
if target.operating_system != OperatingSystem::Linux {
return Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: "cargo-build is not a supported cross-compilation method for this combination"
.to_string(),
});
}
let env = match target.environment {
Environment::Gnu => "gnu",
Environment::Musl => "musl",
_ => {
return Err(DistError::UnsupportedCrossCompile {
host: host.clone(),
target: target.clone(),
details: format!(
"cargo-build is not a supported cross-compilation method for {}",
target.environment
),
});
}
};
let linux_arch = match target.architecture {
// Strip ISA exts part from riscv triple. e.g. riscv64gc -> riscv64
Architecture::Riscv64(_) => "riscv64".into(),
Architecture::Riscv32(_) => "riscv32".into(),
arch => arch.into_str(),
};
Ok(format!("{linux_arch}-linux-{env}-gcc"))
}

/// Generate a `cargo build` command
pub fn make_build_cargo_target_command(
host: &Triple,
Expand Down Expand Up @@ -190,6 +282,16 @@ pub fn make_build_cargo_target_command(
None => {
command.arg("build");
}
Some(CargoBuildWrapper::Plain) => {
command.env(
format!(
"CARGO_TARGET_{}_LINKER",
target.to_string().to_uppercase().replace('-', "_")
),
target_gcc_linker(host, &target)?,
);
command.arg("build");
}
Some(CargoBuildWrapper::ZigBuild) => {
if auditable {
return Err(DistError::CannotDoCargoAuditableAndCrossCompile {
Expand Down
22 changes: 16 additions & 6 deletions cargo-dist/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! It's currently not terribly well-suited to being used as a pure library because it happily
//! writes to stderr/stdout whenever it pleases. Suboptimal for a library.

use std::io::Write;
use std::{borrow::Cow, io::Write};

use announce::TagSettings;
use axoasset::LocalAsset;
Expand Down Expand Up @@ -79,6 +79,7 @@ pub fn do_env_test(cfg: &Config) -> DistResult<()> {

let tools = dist.tools;
let host = tools.host_target.parse()?;
let mut plain_cross_targets = Vec::new();

for step in dist.local_build_steps.iter() {
// Can't require cross-compilation tools if we aren't compiling.
Expand All @@ -92,6 +93,9 @@ pub fn do_env_test(cfg: &Config) -> DistResult<()> {
let wrapper = tasks::build_wrapper_for_cross(&host, &target)?;

match wrapper {
Some(CargoBuildWrapper::Plain) => {
plain_cross_targets.push(target);
}
Some(CargoBuildWrapper::Xwin) => {
need_xwin = true;
}
Expand All @@ -109,12 +113,18 @@ pub fn do_env_test(cfg: &Config) -> DistResult<()> {
//
// bool::then(f) returns an Option, so we start with a
// Vec<Option<Result<&Tool, DistResult>>>.
let all_tools: Vec<Option<DistResult<&Tool>>> = vec![
need_cargo_auditable.then(|| tools.cargo_auditable()),
need_omnibor.then(|| tools.omnibor()),
need_xwin.then(|| tools.cargo_xwin()),
need_zigbuild.then(|| tools.cargo_zigbuild()),
let mut all_tools: Vec<Option<DistResult<Cow<Tool>>>> = vec![
need_cargo_auditable.then(|| tools.cargo_auditable().map(Cow::Borrowed)),
need_omnibor.then(|| tools.omnibor().map(Cow::Borrowed)),
need_xwin.then(|| tools.cargo_xwin().map(Cow::Borrowed)),
need_zigbuild.then(|| tools.cargo_zigbuild().map(Cow::Borrowed)),
];
all_tools.extend(
plain_cross_targets
.iter()
.map(|t| Tools::gcc_cross_toolchain(&host, t).map(Cow::Owned))
.map(Some),
);

// Drop `None`s, then extract the values from the remaining `Option`s.
let needed_tools = all_tools.into_iter().flatten();
Expand Down
25 changes: 23 additions & 2 deletions cargo-dist/src/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@
use std::collections::BTreeMap;

use crate::backend::installer::{ExecutableZipFragment, HomebrewImpl};
use crate::build::cargo::target_gcc_linker;
use crate::platform::targets::{
TARGET_ARM64_LINUX_GNU, TARGET_ARM64_MAC, TARGET_X64_LINUX_GNU, TARGET_X64_MAC,
};
use axoasset::AxoClient;
use axoprocess::Cmd;
use axoproject::{PackageId, PackageIdx, WorkspaceGraph};
use camino::{Utf8Path, Utf8PathBuf};
use dist_schema::target_lexicon::{OperatingSystem, Triple};
use dist_schema::target_lexicon::{Architecture, OperatingSystem, Triple};
use dist_schema::{
ArtifactId, BuildEnvironment, DistManifest, HomebrewPackageName, SystemId, SystemInfo,
TripleName, TripleNameRef,
Expand Down Expand Up @@ -333,6 +334,16 @@ impl Tools {
tool: "cargo-zigbuild".to_owned(),
})
}

/// Returns gcc cross toolchain or an error
pub fn gcc_cross_toolchain(host: &Triple, target: &Triple) -> DistResult<Tool> {
let linker = target_gcc_linker(host, target)?;
if let Some(tool) = find_tool(&linker, "--version") {
Ok(tool)
} else {
Err(DistError::ToolMissing { tool: linker })
}
}
}

/// Info about the cargo toolchain we're using
Expand Down Expand Up @@ -461,6 +472,10 @@ pub struct CargoBuildStep {
/// A wrapper to use instead of `cargo build`, generally used for cross-compilation
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CargoBuildWrapper {
/// Run 'cargo build' to cross-compile, e.g. from `x86_64-unknown-linux-gnu` to `aarch64-unknown-linux-gnu`
/// This wrapper sets the target linker for `cargo build`. e.g. `aarch64-linux-gnu-gcc`
Plain,

/// Run 'cargo zigbuild' to cross-compile, e.g. from `x86_64-unknown-linux-gnu` to `aarch64-unknown-linux-gnu`
/// cf. <https://github.com/rust-cross/cargo-zigbuild>
ZigBuild,
Expand All @@ -473,6 +488,7 @@ pub enum CargoBuildWrapper {
impl std::fmt::Display for CargoBuildWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(match self {
CargoBuildWrapper::Plain => "cargo-build",
CargoBuildWrapper::ZigBuild => "cargo-zigbuild",
CargoBuildWrapper::Xwin => "cargo-xwin",
})
Expand Down Expand Up @@ -510,7 +526,12 @@ pub fn build_wrapper_for_cross(
OperatingSystem::Linux => match host.operating_system {
OperatingSystem::Linux | OperatingSystem::Darwin | OperatingSystem::Windows => {
// zigbuild works for e.g. x86_64-unknown-linux-gnu => aarch64-unknown-linux-gnu
Ok(Some(CargoBuildWrapper::ZigBuild))
match target.architecture {
Architecture::Riscv64(_) | Architecture::LoongArch64 => {
Ok(Some(CargoBuildWrapper::Plain))
},
_ => Ok(Some(CargoBuildWrapper::ZigBuild))
}
}
_ => {
Err(DistError::UnsupportedCrossCompile {
Expand Down