Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ concurrency:
group: CI-${{ github.ref }}
# Queue on all branches and tags, but only cancel overlapping PR burns.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/') }}

jobs:
setup:
name: Check GitHub Organization
Expand All @@ -36,7 +37,7 @@ jobs:
cross-build:
name: "Cross-build pexrc"
needs: setup
runs-on: ubuntu-22.04-arm
runs-on: ubuntu-24.04-arm
# N.B.: We break these up just to save wall time; they all can be built on 1 machine in ~40
# minutes; this gets us to a ~20 minute long-pole.
strategy:
Expand Down Expand Up @@ -64,7 +65,7 @@ jobs:
- name: Checkout Pexrc
uses: actions/checkout@v6
- name: Build pexrc binary for all targets.
run: cargo run -p package -- --profile release ${{ matrix.targets }}
run: cargo run -p package -- --profile release -o dist ${{ matrix.targets }}

tests:
name: "${{ matrix.name }} tests"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Rust build
/target/
/dist/

# Python "build"
*.py[cdo]
Expand All @@ -8,4 +9,3 @@ __pycache__/
/.pytest_cache/
/.ruff_cache/
/.venv/
/python/pexrc/__pex__/
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use pexrc_build_system::{
ClassifiedTargets,
ClibConfiguration,
FoundTool,
Target,
classify_targets,
ensure_tools_installed,
};
Expand Down Expand Up @@ -91,14 +92,14 @@ fn main() -> anyhow::Result<()> {
&["zigbuild", "--target-dir", tgt_arg],
clib.profile,
&found_tools,
targets.iter_zigbuild_targets(),
targets.iter_zigbuild_targets().map(Target::zigbuild_target),
)?;
custom_cargo_build(
&cargo,
&["xwin", "build", "--target-dir", tgt_arg],
clib.profile,
&found_tools,
targets.iter_xwin_targets(),
targets.iter_xwin_targets().map(Target::as_str),
)?;
collect_clibs(&targets, &tgt_path, clib, &clibs_dir, true)
} else {
Expand Down Expand Up @@ -182,7 +183,7 @@ fn collect_clibs<'a>(
}
let mut dst = File::create(clibs_dir.join(format!(
"{target}.{clib_name}",
target = target.python_identifier()
target = target.simplified_target_triple()
)))?;
if compress {
let encoder = zstd::Encoder::new(dst, clib.compression_level)?;
Expand Down
16 changes: 4 additions & 12 deletions crates/boot/src/boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,11 @@ def current(cls):
def __init__(
self,
name,
vendor, # type: str
lib_extension, # type: str
lib_prefix="", # type: str
alt_os=None, # type: Optional[str]
):
# type: (...) -> None
self.name = name
self._os = alt_os or name
self._vendor = vendor
self._lib_prefix = lib_prefix
self._lib_extension = lib_extension

Expand All @@ -117,9 +113,7 @@ def target_triple(
abi=None, # type: Optional[ABI]
):
# type: (...) -> str
return "{arch}-{vendor}-{os}{abi}".format(
arch=arch, vendor=self._vendor, os=self._os, abi="-" + abi.name if abi else ""
)
return "{arch}-{os}{abi}".format(arch=arch, os=self.name, abi="-" + abi.name if abi else "")

def library_file_name(self, lib_name):
# type: (str) -> str
Expand All @@ -132,11 +126,9 @@ def __str__(self):
return self.name


LINUX = OperatingSystem("linux", vendor="unknown", lib_prefix="lib", lib_extension="so")
MACOS = OperatingSystem(
"macos", vendor="apple", alt_os="darwin", lib_prefix="lib", lib_extension="dylib"
)
WINDOWS = OperatingSystem("windows", vendor="pc", lib_extension="dll")
LINUX = OperatingSystem("linux", lib_prefix="lib", lib_extension="so")
MACOS = OperatingSystem("macos", lib_prefix="lib", lib_extension="dylib")
WINDOWS = OperatingSystem("windows", lib_extension="dll")

CURRENT_OS = OperatingSystem.current()

Expand Down
1 change: 1 addition & 0 deletions crates/cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow = { workspace = true }
base64 = { workspace = true }
dirs = { workspace = true }
fs-err = { workspace = true }
hex = { workspace = true }
logging_timer = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
5 changes: 5 additions & 0 deletions crates/cache/src/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ impl Fingerprint {
pub fn base64_digest(&self) -> String {
URL_SAFE_NO_PAD.encode(&self.0)
}

#[time("debug", "Fingerprint.{}")]
pub fn hex_digest(&self) -> String {
hex::encode(&self.0)
}
}

impl Display for Fingerprint {
Expand Down
4 changes: 4 additions & 0 deletions crates/package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ edition = "2024"
[dependencies]
anstream = { workspace = true }
anyhow = { workspace = true }
cache = { path = "../cache" }
clap = { workspace = true }
clap-verbosity-flag = { workspace = true }
colorchoice-clap = { workspace = true }
env_logger = { workspace = true }
fs-err = { workspace = true }
owo-colors = { workspace = true }
pexrc-build-system = { path = "../pexrc-build-system" }
platform = { path = "../platform" }
sha2 = { workspace = true }
111 changes: 89 additions & 22 deletions crates/package/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::string::ToString;
use std::sync::LazyLock;
use std::{env, fs};
use std::{cmp, env, io};

use anyhow::{anyhow, bail};
use cache::Fingerprint;
use clap::builder::Str;
use clap::{ArgAction, Parser};
use fs_err as fs;
use fs_err::File;
use owo_colors::OwoColorize;
use pexrc_build_system::{all_targets, classify_targets, ensure_tools_installed};
use pexrc_build_system::{Target, all_targets, classify_targets, ensure_tools_installed};
use sha2::{Digest, Sha256};

static CARGO: LazyLock<PathBuf> = LazyLock::new(|| env!("CARGO").into());

Expand Down Expand Up @@ -87,6 +91,9 @@ struct Cli {
#[arg(action=ArgAction::Append)]
#[arg(value_parser=clap::builder::PossibleValuesParser::new(AVAILABLE_TARGETS.iter()))]
targets: Vec<String>,

#[arg(short = 'o', long)]
dist_dir: Option<PathBuf>,
}

fn main() -> anyhow::Result<()> {
Expand Down Expand Up @@ -118,43 +125,51 @@ fn main() -> anyhow::Result<()> {
let classified_targets = classify_targets(&rust_toolchain_contents, &glibc)?;

let profile = cli.profile.as_deref().unwrap_or("dev");
let (profile_dir_name, profile_target_suffix) = if profile == "dev" {
("debug", Some("-debug"))
} else {
(profile, None)
};

if cli.targets.is_empty() {
let built = if cli.targets.is_empty() {
let result = Command::new(cargo)
.args(["build", "--profile", profile])
.env("PEXRC_TARGETS", "all")
.spawn()?
.wait()?;
if !result.success() {
bail!("Build via cargo build failed!");
}
let current_target = Target::current(&glibc);
vec![(
target_dir
.join(profile_dir_name)
.join(current_target.binary_name("pexrc", None).as_ref()),
current_target.fully_qualified_binary_name("pexrc", profile_target_suffix),
)]
} else {
let targeted: HashSet<String> = if cli.targets.contains(&ALL_TARGETS) {
AVAILABLE_TARGETS.iter().map(Str::to_string).collect()
} else {
cli.targets.into_iter().collect()
};
let mut built: Vec<(PathBuf, String)> = Vec::with_capacity(targeted.len());
let zigbuild_targets = classified_targets
.iter_zigbuild_targets()
.filter(|target| {
// Strip the `.{glibc-version}` suffix from `*-gnu.{glibc-version}` targets.
// TODO: Encode classified targets such that we don't need to use string parsing
// here to undo earlier string concatenation of the glibc-version when classifying
// the targets.
let target = if target.contains("-gnu.")
&& let Some(target) = target.splitn(2, ".").take(1).next()
{
target
} else {
target
};
targeted.contains(target)
})
.filter(|target| targeted.contains(target.as_str()))
.collect::<Vec<_>>();
if !zigbuild_targets.is_empty() {
let mut command = Command::new(cargo);
command.args(["zigbuild", "--profile", profile]);
for target in zigbuild_targets {
command.args(["--target", target]);
command.args(["--target", target.zigbuild_target()]);
built.push((
target_dir
.join(target.as_str())
.join(profile_dir_name)
.join(target.binary_name("pexrc", None).as_ref()),
target.fully_qualified_binary_name("pexrc", profile_target_suffix),
));
}
command.env("PEXRC_TARGETS", "all");
for found_tool in &found_tools {
Expand All @@ -168,13 +183,20 @@ fn main() -> anyhow::Result<()> {

let xwin_targets = classified_targets
.iter_xwin_targets()
.filter(|target| targeted.contains(*target))
.filter(|target| targeted.contains(target.as_str()))
.collect::<Vec<_>>();
if !xwin_targets.is_empty() {
let mut command = Command::new(cargo);
command.args(["xwin", "build", "--profile", profile]);
for target in xwin_targets {
command.args(["--target", target]);
command.args(["--target", target.as_str()]);
built.push((
target_dir
.join(target.as_str())
.join(profile_dir_name)
.join(target.binary_name("pexrc", None).as_ref()),
target.fully_qualified_binary_name("pexrc", profile_target_suffix),
));
}
command.env("PEXRC_TARGETS", "all");
for found_tool in &found_tools {
Expand All @@ -185,8 +207,53 @@ fn main() -> anyhow::Result<()> {
bail!("Cross-build via cargo-xwin failed!");
}
}
}
built
};

anstream::println!("{}", "Build complete!".green());
if let Some(dist_dir) = cli.dist_dir {
let mut max_width = 0;
for (_, dst_file_name) in &built {
max_width = cmp::max(max_width, dst_file_name.len());
}
fs::create_dir_all(&dist_dir)?;
let count = built.len();
anstream::println!(
"Built {count} {binaries} to {dist_dir}:",
binaries = if count == 1 { "binary" } else { "binaries" },
dist_dir = dist_dir.display()
);
let dist_dir = dist_dir.canonicalize()?;
for (idx, (src, dst_file_name)) in built.iter().enumerate() {
let dst = dist_dir.join(dst_file_name);
if dst.exists() {
fs::remove_file(&dst)?;
}
let (size, fingerprint) = hash_file(src)?;
platform::link_or_copy(src, &dst)?;
fs::write(
dst.with_added_extension("sha256"),
format!(
"{hex_digest} *{dst_file_name}",
hex_digest = fingerprint.hex_digest()
),
)?;
anstream::println!(
"{idx:>3}. {path} {pad}{size:<9} bytes {alg}:{fingerprint}",
idx = (idx + 1).yellow(),
path = dst_file_name.blue(),
pad = " ".repeat(max_width - dst_file_name.len()),
alg = "sha256-base64".green(),
fingerprint = fingerprint.base64_digest().green(),
)
}
} else {
anstream::println!("{}", "Build complete!".green());
}
Ok(())
}

fn hash_file(path: &Path) -> anyhow::Result<(u64, Fingerprint)> {
let mut digest = Sha256::new();
let size = io::copy(&mut File::open(path)?, &mut digest)?;
Ok((size, Fingerprint::new(digest)))
}
Loading