gh-workflow-parser 0.4.0

Parse GitHub workflows and do stuff like create issues
Documentation
use std::error::Error;
use std::io::Read;
use std::{env, fs, path::Path};

// Convenience macro for printing warnings during the build process
//
// Currently the only way to print "info" messages during the build process (see: https://github.com/rust-lang/cargo/issues/985)
macro_rules! print_warn {
    ($($tokens: tt)*) => {
        println!("cargo:warning={}", format!($($tokens)*))
    }
}

/// Max upload size for crates.io is 10 MiB
const MAX_CRATES_IO_UPLOAD_SIZE: usize = 1024 * 1024 * 10;

/// Path to the GitHub CLI file
const GH_CLI_PATH: &str = "gh_cli/gh";
/// Path to the GitHub CLI compressed file
const GH_CLI_COMPRESSED_PATH: &str = "gh_cli/compressed/gh_cli_bz2";

/// Name of the file that contains the byte array for the GitHub CLI file
const INCLUDE_GH_CLI_FILE: &str = "include_gh_cli.rs";

/// Compression for the GitHub CLI file (set the compression even higher if the file size is too large for crates.io)
pub fn bzip2_compress(input: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
    let mut e = bzip2::bufread::BzEncoder::new(input, bzip2::Compression::new(9));
    let mut out = Vec::new();
    e.read_to_end(&mut out)?;
    Ok(out)
}

fn main() {
    // Re-run this build script if the build script itself changes or if the gh_cli/gh file changes
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=gh_cli/gh");

    let env_out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set");
    let out_dir_path = Path::new(&env_out_dir);
    let gh_cli_path = Path::new(GH_CLI_PATH);

    if !gh_cli_path.exists() {
        print_warn!("{GH_CLI_PATH} does not exist - relying on the archived binary in {GH_CLI_COMPRESSED_PATH}");
    } else {
        let gh_cli_bytes = fs::read(gh_cli_path).expect("Failed to read gh_cli/gh");
        let compressed_gh_cli_bytes = bzip2_compress(&gh_cli_bytes).unwrap();
        assert!(compressed_gh_cli_bytes.len() < MAX_CRATES_IO_UPLOAD_SIZE);
        // Write to the `gh_cli/compressed` file that will be included in the crates.io package
        fs::write(GH_CLI_COMPRESSED_PATH, compressed_gh_cli_bytes)
            .expect("Failed to write gh_cli_bz2");
    }

    include_gh_cli(out_dir_path).unwrap();
}

fn include_gh_cli(out_dir: &Path) -> Result<(), Box<dyn Error>> {
    let gh_cli_bytes = fs::read(GH_CLI_COMPRESSED_PATH).expect("Failed to read gh_cli/gh");
    let gh_cli_size = gh_cli_bytes.len();
    assert!(
        gh_cli_size < MAX_CRATES_IO_UPLOAD_SIZE,
        "gh_cli_bz2 size exceeds maximum size for crates.io"
    );

    let gh_cli_path = out_dir.join("gh_cli_bz2");
    fs::write(&gh_cli_path, gh_cli_bytes).expect("Failed to write gh_cli_bz2");

    let include_gh_cli_rs_contents = format_include_gh_cli_rs(gh_cli_size, &gh_cli_path);

    let include_gh_cli_rs_path = out_dir.join(INCLUDE_GH_CLI_FILE);
    fs::write(include_gh_cli_rs_path, include_gh_cli_rs_contents)
        .expect("Failed to write include_gh_cli.rs");

    Ok(())
}

/// Format the contents of the `include_gh_cli.rs` file
fn format_include_gh_cli_rs(gh_cli_compressed_size: usize, gh_cli_path: &Path) -> String {
    format!(
        r#"
        pub const GH_CLI_BYTES: &[u8; {gh_cli_compressed_size}] = include_bytes!({gh_cli_path:?});
        "#
    )
}