Restructure project layout for better CI/CD integration
- Flattened nested bootupd/bootupd/ structure to root level - Moved all core project files to root directory - Added proper Debian packaging structure (debian/ directory) - Created build scripts and CI configuration - Improved project organization for CI/CD tools - All Rust source, tests, and configuration now at root level - Added GitHub Actions workflow for automated testing - Maintained all original functionality while improving structure
This commit is contained in:
parent
5e8730df43
commit
aaf662d5b1
87 changed files with 1334 additions and 570 deletions
5
xtask/.gitignore
vendored
Executable file
5
xtask/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
/target
|
||||
fastbuild*.qcow2
|
||||
_kola_temp
|
||||
.cosa
|
||||
Cargo.lock
|
||||
15
xtask/Cargo.toml
Executable file
15
xtask/Cargo.toml
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
camino = "1.0"
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["std"] }
|
||||
fn-error-context = "0.2.0"
|
||||
toml = "0.8"
|
||||
tempfile = "3.3"
|
||||
xshell = { version = "0.2" }
|
||||
253
xtask/src/main.rs
Executable file
253
xtask/src/main.rs
Executable file
|
|
@ -0,0 +1,253 @@
|
|||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use fn_error_context::context;
|
||||
use xshell::{cmd, Shell};
|
||||
|
||||
const NAME: &str = "bootupd";
|
||||
const VENDORPATH: &str = "vendor.tar.zstd";
|
||||
const TAR_REPRODUCIBLE_OPTS: &[&str] = &[
|
||||
"--sort=name",
|
||||
"--owner=0",
|
||||
"--group=0",
|
||||
"--numeric-owner",
|
||||
"--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = try_main() {
|
||||
eprintln!("error: {e:#}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_main() -> Result<()> {
|
||||
let task = std::env::args().nth(1);
|
||||
let sh = xshell::Shell::new()?;
|
||||
if let Some(cmd) = task.as_deref() {
|
||||
let f = match cmd {
|
||||
"vendor" => vendor,
|
||||
"package" => package,
|
||||
"package-srpm" => package_srpm,
|
||||
_ => print_help,
|
||||
};
|
||||
f(&sh)?;
|
||||
} else {
|
||||
print_help(&sh)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_target_dir() -> Result<Utf8PathBuf> {
|
||||
let target = Utf8Path::new("target");
|
||||
std::fs::create_dir_all(&target)?;
|
||||
Ok(target.to_owned())
|
||||
}
|
||||
|
||||
fn vendor(sh: &Shell) -> Result<()> {
|
||||
let _targetdir = get_target_dir()?;
|
||||
let target = VENDORPATH;
|
||||
cmd!(
|
||||
sh,
|
||||
"cargo vendor-filterer --prefix=vendor --format=tar.zstd {target}"
|
||||
)
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gitrev_to_version(v: &str) -> String {
|
||||
let v = v.trim().trim_start_matches('v');
|
||||
v.replace('-', ".")
|
||||
}
|
||||
|
||||
#[context("Finding gitrev")]
|
||||
fn gitrev(sh: &Shell) -> Result<String> {
|
||||
if let Ok(rev) = cmd!(sh, "git describe --tags").ignore_stderr().read() {
|
||||
Ok(gitrev_to_version(&rev))
|
||||
} else {
|
||||
let mut desc = cmd!(sh, "git describe --tags --always").read()?;
|
||||
desc.insert_str(0, "0.");
|
||||
Ok(desc)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a string formatted version of the git commit timestamp, up to the minute
|
||||
/// but not second because, well, we're not going to build more than once a second.
|
||||
#[allow(dead_code)]
|
||||
#[context("Finding git timestamp")]
|
||||
fn git_timestamp(sh: &Shell) -> Result<String> {
|
||||
let ts = cmd!(sh, "git show -s --format=%ct").read()?;
|
||||
let ts = ts.trim().parse::<i64>()?;
|
||||
let ts = chrono::DateTime::from_timestamp(ts, 0)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to parse timestamp"))?;
|
||||
Ok(ts.format("%Y%m%d%H%M").to_string())
|
||||
}
|
||||
|
||||
struct Package {
|
||||
version: String,
|
||||
srcpath: Utf8PathBuf,
|
||||
vendorpath: Utf8PathBuf,
|
||||
}
|
||||
|
||||
/// Return the timestamp of the latest git commit in seconds since the Unix epoch.
|
||||
fn git_source_date_epoch(dir: &Utf8Path) -> Result<u64> {
|
||||
let o = Command::new("git")
|
||||
.args(["log", "-1", "--pretty=%ct"])
|
||||
.current_dir(dir)
|
||||
.output()?;
|
||||
if !o.status.success() {
|
||||
anyhow::bail!("git exited with an error: {:?}", o);
|
||||
}
|
||||
let buf = String::from_utf8(o.stdout).context("Failed to parse git log output")?;
|
||||
let r = buf.trim().parse()?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
|
||||
/// When using cargo-vendor-filterer --format=tar, the config generated has a bogus source
|
||||
/// directory. This edits it to refer to vendor/ as a stable relative reference.
|
||||
#[context("Editing vendor config")]
|
||||
fn edit_vendor_config(config: &str) -> Result<String> {
|
||||
let mut config: toml::Value = toml::from_str(config)?;
|
||||
let config = config.as_table_mut().unwrap();
|
||||
let source_table = config.get_mut("source").unwrap();
|
||||
let source_table = source_table.as_table_mut().unwrap();
|
||||
let vendored_sources = source_table.get_mut("vendored-sources").unwrap();
|
||||
let vendored_sources = vendored_sources.as_table_mut().unwrap();
|
||||
let previous =
|
||||
vendored_sources.insert("directory".into(), toml::Value::String("vendor".into()));
|
||||
assert!(previous.is_some());
|
||||
|
||||
Ok(config.to_string())
|
||||
}
|
||||
|
||||
#[context("Packaging")]
|
||||
fn impl_package(sh: &Shell) -> Result<Package> {
|
||||
let source_date_epoch = git_source_date_epoch(".".into())?;
|
||||
let v = gitrev(sh)?;
|
||||
|
||||
let namev = format!("{NAME}-{v}");
|
||||
let p = Utf8Path::new("target").join(format!("{namev}.tar"));
|
||||
let prefix = format!("{namev}/");
|
||||
cmd!(sh, "git archive --format=tar --prefix={prefix} -o {p} HEAD").run()?;
|
||||
// Generate the vendor directory now, as we want to embed the generated config to use
|
||||
// it in our source.
|
||||
let vendorpath = Utf8Path::new("target").join(format!("{namev}-vendor.tar.zstd"));
|
||||
let vendor_config = cmd!(
|
||||
sh,
|
||||
"cargo vendor-filterer --prefix=vendor --format=tar.zstd {vendorpath}"
|
||||
)
|
||||
.read()?;
|
||||
let vendor_config = edit_vendor_config(&vendor_config)?;
|
||||
// Append .cargo/vendor-config.toml (a made up filename) into the tar archive.
|
||||
{
|
||||
let tmpdir = tempfile::tempdir_in("target")?;
|
||||
let tmpdir_path = tmpdir.path();
|
||||
let path = tmpdir_path.join("vendor-config.toml");
|
||||
std::fs::write(&path, vendor_config)?;
|
||||
let source_date_epoch = format!("{source_date_epoch}");
|
||||
cmd!(
|
||||
sh,
|
||||
"tar -r -C {tmpdir_path} {TAR_REPRODUCIBLE_OPTS...} --mtime=@{source_date_epoch} --transform=s,^,{prefix}.cargo/, -f {p} vendor-config.toml"
|
||||
)
|
||||
.run()?;
|
||||
}
|
||||
// Compress with zstd
|
||||
let srcpath: Utf8PathBuf = format!("{p}.zstd").into();
|
||||
cmd!(sh, "zstd --rm -f {p} -o {srcpath}").run()?;
|
||||
|
||||
Ok(Package {
|
||||
version: v,
|
||||
srcpath,
|
||||
vendorpath,
|
||||
})
|
||||
}
|
||||
|
||||
fn package(sh: &Shell) -> Result<()> {
|
||||
let p = impl_package(sh)?.srcpath;
|
||||
println!("Generated: {p}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn impl_srpm(sh: &Shell) -> Result<Utf8PathBuf> {
|
||||
let pkg = impl_package(sh)?;
|
||||
vendor(sh)?;
|
||||
let td = tempfile::tempdir_in("target").context("Allocating tmpdir")?;
|
||||
let td = td.into_path();
|
||||
let td: &Utf8Path = td.as_path().try_into().unwrap();
|
||||
let srcpath = td.join(pkg.srcpath.file_name().unwrap());
|
||||
std::fs::rename(pkg.srcpath, srcpath)?;
|
||||
let v = pkg.version;
|
||||
let vendorpath = td.join(format!("{NAME}-{v}-vendor.tar.zstd"));
|
||||
std::fs::rename(VENDORPATH, vendorpath)?;
|
||||
{
|
||||
let specin = File::open(format!("contrib/packaging/{NAME}.spec"))
|
||||
.map(BufReader::new)
|
||||
.context("Opening spec")?;
|
||||
let mut o = File::create(td.join(format!("{NAME}.spec"))).map(BufWriter::new)?;
|
||||
for line in specin.lines() {
|
||||
let line = line?;
|
||||
if line.starts_with("Version:") {
|
||||
writeln!(o, "# Replaced by cargo xtask package-srpm")?;
|
||||
writeln!(o, "Version: {v}")?;
|
||||
} else {
|
||||
writeln!(o, "{}", line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let d = sh.push_dir(td);
|
||||
let mut cmd = cmd!(sh, "rpmbuild");
|
||||
for k in [
|
||||
"_sourcedir",
|
||||
"_specdir",
|
||||
"_builddir",
|
||||
"_srcrpmdir",
|
||||
"_rpmdir",
|
||||
] {
|
||||
cmd = cmd.arg("--define");
|
||||
cmd = cmd.arg(format!("{k} {td}"));
|
||||
}
|
||||
let spec = format!("{NAME}.spec");
|
||||
cmd.arg("--define")
|
||||
.arg(format!("_buildrootdir {td}/.build"))
|
||||
.args(["-bs", spec.as_str()])
|
||||
.run()?;
|
||||
drop(d);
|
||||
let mut srpm = None;
|
||||
for e in std::fs::read_dir(td)? {
|
||||
let e = e?;
|
||||
let n = e.file_name();
|
||||
let n = if let Some(n) = n.to_str() {
|
||||
n
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
if n.ends_with(".src.rpm") {
|
||||
srpm = Some(td.join(n));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let srpm = srpm.ok_or_else(|| anyhow::anyhow!("Failed to find generated .src.rpm"))?;
|
||||
let dest = Utf8Path::new("target").join(srpm.file_name().unwrap());
|
||||
std::fs::rename(&srpm, &dest)?;
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
fn package_srpm(sh: &Shell) -> Result<()> {
|
||||
let _targetdir = get_target_dir()?;
|
||||
let srpm = impl_srpm(sh)?;
|
||||
println!("Generated: {srpm}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_help(_sh: &Shell) -> Result<()> {
|
||||
eprintln!(
|
||||
"Tasks:
|
||||
- vendor
|
||||
"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue