summaryrefslogtreecommitdiff
path: root/src/combiner.rs
blob: 006a40c69cd482f583aa33573e402bea0e58b210 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use super::Scripter;
use super::Tarballer;
use crate::{
    compression::{CompressionFormat, CompressionFormats},
    util::*,
};
use anyhow::{bail, Context, Result};
use std::io::{Read, Write};
use std::path::Path;
use tar::Archive;

actor! {
    #[derive(Debug)]
    pub struct Combiner {
        /// The name of the product, for display.
        product_name: String = "Product",

        /// The name of the package  tarball.
        package_name: String = "package",

        /// The directory under lib/ where the manifest lives.
        rel_manifest_dir: String = "packagelib",

        /// The string to print after successful installation.
        success_message: String = "Installed.",

        /// Places to look for legacy manifests to uninstall.
        legacy_manifest_dirs: String = "",

        /// Installers to combine.
        input_tarballs: String = "",

        /// Directory containing files that should not be installed.
        non_installed_overlay: String = "",

        /// The directory to do temporary work.
        work_dir: String = "./workdir",

        /// The location to put the final image and tarball.
        output_dir: String = "./dist",

        /// The formats used to compress the tarball
        compression_formats: CompressionFormats = CompressionFormats::default(),
    }
}

impl Combiner {
    /// Combines the installer tarballs.
    pub fn run(self) -> Result<()> {
        create_dir_all(&self.work_dir)?;

        let package_dir = Path::new(&self.work_dir).join(&self.package_name);
        if package_dir.exists() {
            remove_dir_all(&package_dir)?;
        }
        create_dir_all(&package_dir)?;

        // Merge each installer into the work directory of the new installer.
        let components = create_new_file(package_dir.join("components"))?;
        for input_tarball in self
            .input_tarballs
            .split(',')
            .map(str::trim)
            .filter(|s| !s.is_empty())
        {
            // Extract the input tarballs
            let compression =
                CompressionFormat::detect_from_path(input_tarball).ok_or_else(|| {
                    anyhow::anyhow!("couldn't figure out the format of {}", input_tarball)
                })?;
            Archive::new(compression.decode(input_tarball)?)
                .unpack(&self.work_dir)
                .with_context(|| {
                    format!(
                        "unable to extract '{}' into '{}'",
                        &input_tarball, self.work_dir
                    )
                })?;

            let pkg_name =
                input_tarball.trim_end_matches(&format!(".tar.{}", compression.extension()));
            let pkg_name = Path::new(pkg_name).file_name().unwrap();
            let pkg_dir = Path::new(&self.work_dir).join(&pkg_name);

            // Verify the version number.
            let mut version = String::new();
            open_file(pkg_dir.join("rust-installer-version"))
                .and_then(|mut file| Ok(file.read_to_string(&mut version)?))
                .with_context(|| format!("failed to read version in '{}'", input_tarball))?;
            if version.trim().parse() != Ok(crate::RUST_INSTALLER_VERSION) {
                bail!("incorrect installer version in {}", input_tarball);
            }

            // Copy components to the new combined installer.
            let mut pkg_components = String::new();
            open_file(pkg_dir.join("components"))
                .and_then(|mut file| Ok(file.read_to_string(&mut pkg_components)?))
                .with_context(|| format!("failed to read components in '{}'", input_tarball))?;
            for component in pkg_components.split_whitespace() {
                // All we need to do is copy the component directory. We could
                // move it, but rustbuild wants to reuse the unpacked package
                // dir for OS-specific installers on macOS and Windows.
                let component_dir = package_dir.join(&component);
                create_dir(&component_dir)?;
                copy_recursive(&pkg_dir.join(&component), &component_dir)?;

                // Merge the component name.
                writeln!(&components, "{}", component).context("failed to write new components")?;
            }
        }
        drop(components);

        // Write the installer version.
        let version = package_dir.join("rust-installer-version");
        writeln!(
            create_new_file(version)?,
            "{}",
            crate::RUST_INSTALLER_VERSION
        )
        .context("failed to write new installer version")?;

        // Copy the overlay.
        if !self.non_installed_overlay.is_empty() {
            copy_recursive(self.non_installed_overlay.as_ref(), &package_dir)?;
        }

        // Generate the install script.
        let output_script = package_dir.join("install.sh");
        let mut scripter = Scripter::default();
        scripter
            .product_name(self.product_name)
            .rel_manifest_dir(self.rel_manifest_dir)
            .success_message(self.success_message)
            .legacy_manifest_dirs(self.legacy_manifest_dirs)
            .output_script(path_to_str(&output_script)?.into());
        scripter.run()?;

        // Make the tarballs.
        create_dir_all(&self.output_dir)?;
        let output = Path::new(&self.output_dir).join(&self.package_name);
        let mut tarballer = Tarballer::default();
        tarballer
            .work_dir(self.work_dir)
            .input(self.package_name)
            .output(path_to_str(&output)?.into())
            .compression_formats(self.compression_formats.clone());
        tarballer.run()?;

        Ok(())
    }
}