rust_release_artefact 0.1.3

Safely extract installable files from Rust release artefacts.
Documentation
// install-component: Install a component from an artefact to a directory.
//
// This example shows how to extract a component from a Rust release artefact
// and install it to a given directory.
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rust_release_artefact as rra;
extern crate tempfile;

use std::env;
use std::error;
use std::fs;
use std::io;
use std::path;
use std::process;

// Ordinarily, the artefact source (such as the release channel
// manifest, or the Content-Type header from an HTTP response) will tell
// you what kind of artefact to expect, so this kind of guessing isn't
// required. It's only because we're reading directly from a file that
// this is necessary.
fn open_artefact(
    src_path: &path::Path,
) -> Result<rra::ExtractedArtefact, Box<error::Error>> {
    // Here we're extracting to a temporary directory, which will hang around
    // until somebody else cleans it up.
    // Note that we are canonicalising the path so that we can exceed the
    // standard Windows 260-character path limit - some artefacts have much
    // deeper directory structures than that.
    let dst_path = tempfile::tempdir()?.into_path().canonicalize()?;
    let artefact_handle = io::BufReader::new(fs::File::open(src_path)?);

    let extension = src_path
        .extension()
        .and_then(|ext| ext.to_str())
        .unwrap_or("");

    Ok(match extension {
        "gz" => rra::ExtractedArtefact::from_tar_gz(artefact_handle, dst_path)?,
        "xz" => rra::ExtractedArtefact::from_tar_xz(artefact_handle, dst_path)?,
        _ => Err(format!("Unrecognised extension: {:?}", extension))?,
    })
}

fn main() {
    env_logger::init();

    info!("Parsing command-line arguments...");
    let args = env::args_os().collect::<Vec<_>>();

    if args.len() != 4 {
        error!("Usage: install-component ARTEFACT COMPONENT DESTINATION");
        process::exit(1);
    }

    let src_path = path::Path::new(&args[1]).canonicalize().unwrap_or_else(
        |err| {
            error!("Could not canonicalize artefact path: {}", err);
            process::exit(1)
        },
    );
    let component_name = args[2].to_str().unwrap_or_else(|| {
        error!("Component name had invalid UTF-8: {:?}", &args[2]);
        process::exit(1)
    });
    let dst_path = path::Path::new(&args[3]);
    fs::create_dir_all(&dst_path).unwrap_or_else(|err| {
        error!("Could not create destination path: {}", err);
        process::exit(1)
    });
    let dst_path = dst_path.canonicalize().unwrap_or_else(|err| {
        error!("Could not canonicalize path: {}", err);
        process::exit(1)
    });

    info!("Extracting artefact to temporary directory...");
    let extracted_artefact = open_artefact(&src_path).unwrap_or_else(|err| {
        error!("{}", err);
        process::exit(1)
    });

    info!("Reading component {:?}...", component_name);
    let component = extracted_artefact
        .components
        .get(component_name)
        .unwrap_or_else(|| {
            error!(
                "Artefact has no component named {:?}, components are {:?}",
                component_name,
                extracted_artefact.components.keys().collect::<Vec<_>>()
            );
            process::exit(1)
        });

    info!("Installing component to {:?}...", dst_path);
    component.install_to(&dst_path).unwrap_or_else(|err| {
        error!("Could not install component: {}", err);
        process::exit(1)
    });
}