diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..0f0e2a3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --frozen --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index 5847d61..0ee1875 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "argh" version = "0.1.13" @@ -248,6 +260,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.6.1" @@ -319,12 +340,40 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "deranged" version = "0.4.0" @@ -334,6 +383,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -433,6 +492,29 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -497,6 +579,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -736,6 +828,17 @@ dependencies = [ "windows-targets 0.53.0", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + [[package]] name = "libssh2-sys" version = "0.3.1" @@ -750,6 +853,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + [[package]] name = "libz-sys" version = "1.1.22" @@ -804,6 +916,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + [[package]] name = "mpdris" version = "1.2.0" @@ -992,6 +1113,15 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags", +] + [[package]] name = "rust-fuzzy-search" version = "0.1.1" @@ -1070,6 +1200,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1176,6 +1317,17 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.20.0" @@ -1313,6 +1465,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "uds_windows" version = "1.1.0" @@ -1365,6 +1523,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1722,6 +1886,28 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xattr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +dependencies = [ + "libc", + "rustix 1.0.7", +] + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "argh", + "flate2", + "hex", + "sha2", + "tar", +] + [[package]] name = "yoke" version = "0.8.0" @@ -1860,6 +2046,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + [[package]] name = "zvariant" version = "5.5.3" diff --git a/Cargo.toml b/Cargo.toml index af3c8a1..35e00f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ authors = [ "jasger9000 | jasger_" ] license = "MIT" repository = "https://github.com/jasger9000/mpdris" +[workspace] +members = [ "xtask" ] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/README.md b/README.md index 1da2a4d..a9935f6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ __Table of Contents:__ To install this application, you can either... - [Use the AUR package (Arch Linux only)](#use-the-aur-package) - [Build the application yourself](#build-the-application-yourself) -- [Install the application from a release binary](#install-using-release-binary) +- [Install the application from a release binary](#install-using-release-binarytarball) ### Use the AUR package > [!IMPORTANT] @@ -80,23 +80,24 @@ You can either build the AUR-package yourself, as detailed below, or use your fa ```bash cargo build --release ``` -3. Copy the resulting file from `target/release/mpdris` to `/usr/local/bin` -4. Copy `resources/mpdris.service` to `/usr/local/lib/systemd/user` (You might have to create that directory first) +3. Move the resulting file from `target/release/mpdris` to `/usr/local/bin` +4. Copy `resources/mpdris.service.local` to `/usr/local/lib/systemd/user` (You might have to create that directory first) and rename it to `mpdris.service` 5. Enable the service to start it with MPD ```bash systemctl --user enable mpdris.service ``` -### Install using release binary -2. Download the correct binary for your architecture +### Install using release binary/tarball 1. Go to the [release tab](https://github.com/jasger9000/mpdris/releases) +2. Download the correct tarball or binary for your architecture - If you don't know what your architecture is, you can find out by running `lscpu` -3. Copy the file to `/usr/local/bin` and rename it to `mpdris` +3. Move the binary to `/usr/local/bin` and rename it to `mpdris` if needed 4. Add the execute permission to the file with ```bash chmod +x /usr/local/bin/mpdris ``` -5. Download and move [mpdris.service](https://github.com/jasger9000/mpdris/blob/main/resources/mpdris.service) to `/usr/local/lib/systemd/user` (You might have to create that directory first) +5. Move `mpdris.service.local` to `/usr/local/lib/systemd/user` (You might have to create that directory first) and rename it to `mpdris.service` + - mpdris.service.local can be found in the release tarball or downloaded [here](https://github.com/jasger9000/mpdris/blob/main/resources/mpdris.service.local) 6. Enable the service to start it with MPD ```bash systemctl --user enable mpdris.service @@ -156,6 +157,13 @@ Contributions are always welcome! If you feel there's something missing/wrong/something that could be improved please open an [issue](https://github.com/jasger9000/mpdris/issues).
Or if you want to add something yourself, just [open a pull request](https://github.com/jasger9000/mpdris/pulls) and I will have a look at it as soon as I can. +## Packaging +If you want to create a package of this application yourself, you can use the xtask cargo subcommand.
+Simply use `cargo xtask build [] [--arch ]` to build the project.
+To create an install from that build, use `cargo xtask install [--arch ]`. Note that you have to run the build command first though.
+You can also just create the manpage structure using `cargo xtask man `.
+To create release assets (tarballs, binaries, SHA256sums) for x86_64, i686 and aarch64 with `cargo xtask make-release-assets`. + ## Licence The Project is Licensed under the [MIT Licence](https://github.com/jasger9000/mpdris/?tab=MIT-1-ov-file) diff --git a/resources/mpdris.service b/resources/mpdris.service index 9f51b81..99b147d 100644 --- a/resources/mpdris.service +++ b/resources/mpdris.service @@ -7,7 +7,7 @@ After=mpd.service Type=notify-reload NotifyAccess=main Restart=on-failure -ExecStart=/usr/local/bin/mpdris --service +ExecStart=/usr/bin/mpdris --service [Install] WantedBy=mpd.service diff --git a/resources/mpdris.service.local b/resources/mpdris.service.local new file mode 100644 index 0000000..9f51b81 --- /dev/null +++ b/resources/mpdris.service.local @@ -0,0 +1,13 @@ +[Unit] +Description=Music Player Daemon MPRIS bridge +BindsTo=mpd.service +After=mpd.service + +[Service] +Type=notify-reload +NotifyAccess=main +Restart=on-failure +ExecStart=/usr/local/bin/mpdris --service + +[Install] +WantedBy=mpd.service diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..099a237 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.98" +argh = "0.1.13" +tar = "0.4.44" +sha2 = "0.10.9" +flate2 = { version = "1.1.2", default-features = false, features = ["zlib-rs"] } +hex = "0.4.3" + diff --git a/xtask/src/args.rs b/xtask/src/args.rs new file mode 100644 index 0000000..e8195f9 --- /dev/null +++ b/xtask/src/args.rs @@ -0,0 +1,68 @@ +use std::{env, path::PathBuf}; + +use argh::FromArgs; + +/// XTasks +#[derive(FromArgs)] +#[argh(help_triggers("-h", "--help"))] +pub(crate) struct Args { + #[argh(subcommand)] + /// the task to execute + pub(crate) task: Task, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +pub(crate) enum Task { + Man(ManTask), + Build(BuildTask), + Install(InstallTask), + CleanDist(CleanTask), + MakeRelease(ReleaseTask), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Write & compress manpages to +#[argh(subcommand, name = "man", help_triggers("-h", "--help"))] +pub(crate) struct ManTask { + #[argh(positional)] + /// the directory to which the compressed manpages should be written to + pub(crate) dir: PathBuf, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Compile/Build all project assets for the default arch or the one provided. +/// Result is written to target/dist/ or if provided. +#[argh(subcommand, name = "build", help_triggers("-h", "--help"))] +pub(crate) struct BuildTask { + #[argh(option, default = "env::consts::ARCH.to_string()")] + /// the arch to compile for + pub(crate) arch: String, + #[argh(positional)] + /// path to install the files to instead of target/dist/ + pub(crate) path: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Create an install using the default arch or if provided. +/// Result is written to target/dist/ or if provided. +/// Note: install does NOT compile anything, for that please use build +#[argh(subcommand, name = "install", help_triggers("-h", "--help"))] +pub(crate) struct InstallTask { + #[argh(option, default = "env::consts::ARCH.to_string()")] + /// the arch to compile for + pub(crate) arch: String, + #[argh(positional)] + /// path to install the files to instead of target/dist/ + pub(crate) path: Option, +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Clean the target/dist directory +#[argh(subcommand, name = "clean-dist", help_triggers("-h", "--help"))] +pub(crate) struct CleanTask {} + +#[derive(FromArgs, PartialEq, Debug)] +/// Create release assets (tarballs, binaries and SHA256 checksums) for x86_64, aarch64, i68 +#[argh(subcommand, name = "make-release-assets", help_triggers("-h", "--help"))] +pub(crate) struct ReleaseTask {} diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs new file mode 100644 index 0000000..219748b --- /dev/null +++ b/xtask/src/dist.rs @@ -0,0 +1,190 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::{env, fs::Permissions, io::Write, os::unix::fs::PermissionsExt, process::Command, sync::Arc}; + +use crate::{DIST_DIR, NAME, PROJECT_ROOT, TARGET_DIR, Task, build_man}; +use anyhow::{Context, Result, anyhow}; +use flate2::{Compression, write::GzEncoder}; +use sha2::{Digest, Sha256}; + +macro_rules! cp { + ($outdir:expr, $src:literal, $dst:literal, $perm:expr) => { + cp!(&$crate::PROJECT_ROOT, $outdir, $src, $dst, $perm) + }; + ($indir:expr, $outdir:expr, $src:literal, $dst:literal, $perm:expr) => { + $crate::dist::copy($indir, $outdir, &::std::format!($src), &::std::format!($dst), $perm) + }; + ($indir:expr, $outdir:expr, $src:literal, $dst:literal) => { + $crate::dist::copy_dir_all($indir.join(::std::format!($src)), $outdir.join(::std::format!($dst))) + }; +} + +fn copy>(indir: &Path, outdir: &Path, src: P, dst: P, perm: u32) -> Result<()> { + let (src, dst) = (indir.join(src), outdir.join(dst)); + fs::copy(&src, &dst).with_context(|| format!("Failed to copy {}", dst.file_name().unwrap().display()))?; + fs::set_permissions(&dst, Permissions::from_mode(perm))?; + Ok(()) +} + +fn copy_dir_all, Q: AsRef>(src: P, dst: Q) -> Result<()> { + fs::create_dir_all(&dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + copy_dir_all(path, dst.as_ref().join(entry.file_name()))?; + } else { + fs::copy(path, dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + +pub(crate) fn clean_dist() -> Result<()> { + let t = Task::new("Cleaning dist"); + + if DIST_DIR.exists() { + fs::remove_dir_all(&*DIST_DIR).with_context(|| "Failed to delete the dist directory")?; + } + + t.success(); + Ok(()) +} + +pub(crate) fn build_binary(arch: &str) -> Result<()> { + let t = Arc::new(Task::new(&format!("Compiling binary for {arch}"))); + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + println!(); + let status = Command::new(cargo) + .current_dir(&*PROJECT_ROOT) + .env("CARGO_TARGET_DIR", &*TARGET_DIR) + .args([ + "build", + "--frozen", + "--release", + &format!("--target={arch}-unknown-linux-gnu"), + ]) + .status() + .with_context(|| "Failed to execute build command")?; + t.fix_text(); + + if !status.success() { + t.failure(); + return Err(anyhow!("Failed to compile binary")); + } + t.success(); + + let t = Task::new("Copying binary to dist"); + fs::create_dir_all(&*DIST_DIR).with_context(|| "Failed to create dist directory")?; + #[rustfmt::skip] + cp!(TARGET_DIR, DIST_DIR, "{arch}-unknown-linux-gnu/release/{NAME}", "{NAME}_{arch}-linux-gnu")?; + t.success(); + + Ok(()) +} + +pub(crate) fn build(path: Option, arch: &str) -> Result<()> { + let outdir = path.unwrap_or(DIST_DIR.to_path_buf()); + + build_binary(arch)?; + build_man(&outdir.join("man"))?; + + Ok(()) +} + +pub(crate) fn install(path: Option, arch: &str) -> Result<()> { + let outdir = path.unwrap_or(DIST_DIR.join(arch)); + + install_create_dirs(&outdir).with_context(|| "Failed to create dist directory structure")?; + install_copy_files(&outdir, arch).with_context(|| "Failed to copy assets to install dir")?; + + Ok(()) +} + +fn install_create_dirs(outdir: &Path) -> Result<()> { + let t = Task::new("Creating directory structure"); + fs::create_dir_all(outdir)?; + fs::create_dir_all(outdir.join("usr/bin"))?; + fs::create_dir_all(outdir.join("usr/lib/systemd/user"))?; + fs::create_dir_all(outdir.join(format!("usr/share/doc/{NAME}")))?; + fs::create_dir_all(outdir.join(format!("usr/share/licenses/{NAME}")))?; + fs::create_dir_all(outdir.join("usr/share/man"))?; + + t.success(); + Ok(()) +} + +#[rustfmt::skip] +fn install_copy_files(outdir: &Path, arch: &str) -> Result<()> { + let t = Task::new("Copying files to dist"); + cp!(&DIST_DIR, outdir, "{NAME}_{arch}-linux-gnu", "usr/bin/{NAME}", 0o755)?; + cp!(outdir, "resources/mpdris.service", "usr/lib/systemd/user/mpdris.service", 0o644)?; + cp!(outdir, "resources/sample.mpdris.conf", "usr/share/doc/{NAME}/sample.mpdris.conf", 0o644)?; + cp!(outdir, "README.md", "usr/share/doc/{NAME}/README.md", 0o644)?; + cp!(outdir, "LICENSE", "usr/share/licenses/{NAME}/LICENSE", 0o644)?; + cp!(DIST_DIR, outdir, "man", "usr/share/man")?; + + t.success(); + Ok(()) +} + +pub(crate) fn make_release_assets() -> Result<()> { + let archs = ["x86_64", "i686", "aarch64"]; + let mandir = DIST_DIR.join("man"); + let mut checksums = (Vec::new(), Vec::new()); + + if !DIST_DIR.is_dir() { + fs::create_dir_all(&*DIST_DIR).with_context(|| "Failed to create dist directory")?; + } + build_man(&mandir)?; + + for arch in archs { + println!("Making release for {arch}"); + + build_binary(arch)?; + let tarball_filename = format!("{NAME}_{arch}.tar.gz"); + let binary_filename = format!("{NAME}_{arch}-linux-gnu"); + let binary_outpath = PROJECT_ROOT.join(DIST_DIR.join(&binary_filename)); + + let t = Task::new("Making tar archive"); + let mut builder = tar::Builder::new(Vec::new()); + builder.mode(tar::HeaderMode::Deterministic); + builder.append_path_with_name(&binary_outpath, NAME)?; + builder.append_path_with_name(PROJECT_ROOT.join("resources/mpdris.service"), "mpdris.service")?; + builder.append_path_with_name(PROJECT_ROOT.join("resources/mpdris.service.local"), "mpdris.service.local")?; + builder.append_path_with_name(PROJECT_ROOT.join("resources/sample.mpdris.conf"), "sample.mpdris.conf")?; + builder.append_path_with_name(PROJECT_ROOT.join("README.md"), "README.md")?; + builder.append_path_with_name(PROJECT_ROOT.join("LICENSE"), "LICENSE")?; + builder.append_dir_all("man", &mandir)?; + + let archive = builder.into_inner()?; + t.success(); + + let t = Task::new("Compressing archive"); + let mut encoder = GzEncoder::new(Vec::new(), Compression::new(9)); + encoder.write_all(&archive)?; + + let compressed = encoder.finish()?; + drop(archive); + t.success(); + + let t = Task::new("Calculating checksums"); + let binary_hash = hex::encode(Sha256::digest(fs::read(&binary_outpath)?)); + let archive_hash = hex::encode(Sha256::digest(&compressed)); + checksums.0.push(format!("{binary_hash} {binary_filename}")); + checksums.1.push(format!("{archive_hash} {tarball_filename}")); + t.success(); + + let t = Task::new("Writing tarball"); + fs::write(DIST_DIR.join(tarball_filename), compressed).with_context(|| "failed to write compressed archive")?; + t.success(); + println!(); + } + + let t = Task::new("Writing checksum file"); + checksums.0.append(&mut checksums.1); + fs::write(DIST_DIR.join("SHA256sums.txt"), checksums.0.join("\n").as_bytes())?; + t.success(); + + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..3b88e58 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,47 @@ +use std::path::{Path, PathBuf}; +use std::{env, process::exit, sync::LazyLock}; + +use anyhow::Result; +use dist::{build, clean_dist, install, make_release_assets}; + +pub(crate) use man::build_man; +pub(crate) use task::Task; + +mod args; +mod dist; +mod man; +mod task; + +static PROJECT_ROOT: LazyLock = LazyLock::new(|| { + Path::new(&env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(1) + .unwrap() + .to_path_buf() +}); +static DIST_DIR: LazyLock = LazyLock::new(|| PROJECT_ROOT.join("target/dist")); +static TARGET_DIR: LazyLock = LazyLock::new(|| PROJECT_ROOT.join("target")); + +const NAME: &str = "mpdris"; +const MANPATH: &str = "resources/man"; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{e:?}"); + exit(-1); + } +} + +fn try_main() -> Result<()> { + use args::Task::*; + + let args: args::Args = argh::from_env(); + + match args.task { + Man(task) => build_man(&task.dir), + Build(task) => build(task.path, &task.arch), + Install(task) => install(task.path, &task.arch), + CleanDist(..) => clean_dist(), + MakeRelease(..) => make_release_assets(), + } +} diff --git a/xtask/src/man.rs b/xtask/src/man.rs new file mode 100644 index 0000000..2cfa3a9 --- /dev/null +++ b/xtask/src/man.rs @@ -0,0 +1,67 @@ +use std::fs::{self, File, Permissions, create_dir_all}; +use std::io::{BufWriter, Write}; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; +use flate2::{Compression, write::GzEncoder}; + +use crate::{MANPATH, PROJECT_ROOT, Task}; + +pub(crate) fn build_man(outdir: &Path) -> Result<()> { + let indir = PROJECT_ROOT.join(MANPATH); + let skip = indir.components().count(); + + if outdir.exists() { + let t = Task::new("Removing old manpage output directory"); + fs::remove_dir_all(outdir).with_context(|| "Failed to delete manpage output directory")?; + fs::create_dir_all(outdir).with_context(|| "Failed to create manpage output directory")?; + t.success(); + } + + let t = Task::new("Building man pages"); + for inpath in search_files_recursive(&indir)? { + let mut outpath: PathBuf = outdir.components().chain(inpath.components().skip(skip)).collect(); + outpath.as_mut_os_string().push(".gz"); + + create_dir_all(outpath.parent().with_context(|| "Failed to get manpage output directory")?) + .with_context(|| "Failed to create manpage output directory")?; + + let infile = fs::read(&inpath).with_context(|| "Failed to read manpage infile")?; + let writer = BufWriter::new(File::create(&outpath).with_context(|| "Failed to open manpage outfile")?); + + let mut encoder = GzEncoder::new(writer, Compression::new(9)); + encoder.write_all(&infile)?; + encoder.try_finish()?; + + fs::set_permissions(&outpath, Permissions::from_mode(0o644)).with_context(|| "Failed to set manpage permissions")?; + } + + t.success(); + Ok(()) +} + +/// Finds all files present in a given start directory, +fn search_files_recursive(start: &Path) -> Result> { + fn inner(start: &Path, result: &mut Vec) -> Result<()> { + if start.is_dir() { + for entry in start.read_dir().with_context(|| "failed to read dir")? { + let entry = entry.with_context(|| "failed to get entry")?; + let path = entry.path(); + + if path.is_dir() { + inner(&path, result)?; + } else { + result.push(path); + } + } + } + + Ok(()) + } + + let mut result: Vec = Vec::new(); + inner(start, &mut result)?; + + Ok(result) +} diff --git a/xtask/src/task.rs b/xtask/src/task.rs new file mode 100644 index 0000000..f43ab40 --- /dev/null +++ b/xtask/src/task.rs @@ -0,0 +1,45 @@ +use std::io::{Write, stdout}; +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; + +#[derive(Debug)] +pub(crate) struct Task { + running: AtomicBool, + text: Box, +} + +impl Task { + pub(crate) fn new(text: &str) -> Self { + // hide cursor and print message + let mut stdout = stdout().lock(); + stdout.write_all(text.as_bytes()).unwrap(); + stdout.flush().unwrap(); + Self { + running: AtomicBool::new(true), + text: text.into(), + } + } + + pub(crate) fn success(&self) { + if self.running.load(Relaxed) { + println!(" - Done"); + self.running.store(false, Relaxed); + } + } + + pub(crate) fn failure(&self) { + if self.running.load(Relaxed) { + println!(" - Failed"); + self.running.store(false, Relaxed); + } + } + + pub(crate) fn fix_text(&self) { + print!("{}", self.text); + } +} + +impl Drop for Task { + fn drop(&mut self) { + self.failure(); + } +}