summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Cargo.toml8
-rwxr-xr-xgen-installer.sh322
-rw-r--r--src/generator.rs211
-rw-r--r--src/lib.rs21
-rw-r--r--src/main.rs58
-rw-r--r--src/main.yml62
7 files changed, 365 insertions, 322 deletions
diff --git a/.gitignore b/.gitignore
index 31ed630..fb017f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
*~
-tmp \ No newline at end of file
+tmp
+target/
+**/*.rs.bk
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..ef1656f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+authors = ["The Rust Project Developers"]
+name = "installer"
+version = "0.0.0"
+
+[dependencies.clap]
+version = "2.22.1"
+features = ["yaml"]
diff --git a/gen-installer.sh b/gen-installer.sh
index a85f1aa..60fac3b 100755
--- a/gen-installer.sh
+++ b/gen-installer.sh
@@ -11,193 +11,6 @@
set -ue
-msg() {
- echo "gen-installer: ${1-}"
-}
-
-step_msg() {
- msg
- msg "$1"
- msg
-}
-
-warn() {
- echo "gen-installer: WARNING: $1" >&2
-}
-
-err() {
- echo "gen-installer: error: $1" >&2
- exit 1
-}
-
-need_ok() {
- if [ $? -ne 0 ]
- then
- err "$1"
- fi
-}
-
-need_cmd() {
- if command -v $1 >/dev/null 2>&1
- then msg "found $1"
- else err "need $1"
- fi
-}
-
-putvar() {
- local t
- local tlen
- eval t=\$$1
- eval tlen=\${#$1}
- if [ $tlen -gt 35 ]
- then
- printf "gen-installer: %-20s := %.35s ...\n" $1 "$t"
- else
- printf "gen-installer: %-20s := %s %s\n" $1 "$t"
- fi
-}
-
-valopt() {
- VAL_OPTIONS="$VAL_OPTIONS $1"
-
- local op=$1
- local default=$2
- shift
- shift
- local doc="$*"
- if [ $HELP -eq 0 ]
- then
- local uop=$(echo $op | tr '[:lower:]' '[:upper:]' | tr '\-' '\_')
- local v="CFG_${uop}"
- eval $v="$default"
- for arg in $CFG_ARGS
- do
- if echo "$arg" | grep -q -- "--$op="
- then
- local val=$(echo "$arg" | cut -f2 -d=)
- eval $v=$val
- fi
- done
- putvar $v
- else
- if [ -z "$default" ]
- then
- default="<none>"
- fi
- op="${default}=[${default}]"
- printf " --%-30s %s\n" "$op" "$doc"
- fi
-}
-
-opt() {
- BOOL_OPTIONS="$BOOL_OPTIONS $1"
-
- local op=$1
- local default=$2
- shift
- shift
- local doc="$*"
- local flag=""
-
- if [ $default -eq 0 ]
- then
- flag="enable"
- else
- flag="disable"
- doc="don't $doc"
- fi
-
- if [ $HELP -eq 0 ]
- then
- for arg in $CFG_ARGS
- do
- if [ "$arg" = "--${flag}-${op}" ]
- then
- op=$(echo $op | tr 'a-z-' 'A-Z_')
- flag=$(echo $flag | tr 'a-z' 'A-Z')
- local v="CFG_${flag}_${op}"
- eval $v=1
- putvar $v
- fi
- done
- else
- if [ ! -z "$META" ]
- then
- op="$op=<$META>"
- fi
- printf " --%-30s %s\n" "$flag-$op" "$doc"
- fi
-}
-
-flag() {
- BOOL_OPTIONS="$BOOL_OPTIONS $1"
-
- local op=$1
- shift
- local doc="$*"
-
- if [ $HELP -eq 0 ]
- then
- for arg in $CFG_ARGS
- do
- if [ "$arg" = "--${op}" ]
- then
- op=$(echo $op | tr 'a-z-' 'A-Z_')
- local v="CFG_${op}"
- eval $v=1
- putvar $v
- fi
- done
- else
- if [ ! -z "$META" ]
- then
- op="$op=<$META>"
- fi
- printf " --%-30s %s\n" "$op" "$doc"
- fi
-}
-
-validate_opt () {
- for arg in $CFG_ARGS
- do
- local is_arg_valid=0
- for option in $BOOL_OPTIONS
- do
- if test --disable-$option = $arg
- then
- is_arg_valid=1
- fi
- if test --enable-$option = $arg
- then
- is_arg_valid=1
- fi
- if test --$option = $arg
- then
- is_arg_valid=1
- fi
- done
- for option in $VAL_OPTIONS
- do
- if echo "$arg" | grep -q -- "--$option="
- then
- is_arg_valid=1
- fi
- done
- if [ "$arg" = "--help" ]
- then
- echo
- echo "No more help available for Configure options,"
- echo "check the Wiki or join our IRC channel"
- break
- else
- if test $is_arg_valid -eq 0
- then
- err "Option '$arg' is not recognized"
- fi
- fi
- done
-}
-
# Prints the absolute path of a directory to stdout
abs_path() {
local path="$1"
@@ -207,138 +20,5 @@ abs_path() {
(unset CDPATH && cd "$path" > /dev/null && pwd)
}
-msg "looking for programs"
-msg
-
-need_cmd cp
-need_cmd rm
-need_cmd mkdir
-need_cmd echo
-need_cmd tr
-need_cmd awk
-
-CFG_ARGS="$@"
-
-HELP=0
-if [ "$1" = "--help" ]
-then
- HELP=1
- shift
- echo
- echo "Usage: $0 [options]"
- echo
- echo "Options:"
- echo
-else
- step_msg "processing arguments"
-fi
-
-OPTIONS=""
-BOOL_OPTIONS=""
-VAL_OPTIONS=""
-
-valopt product-name "Product" "The name of the product, for display"
-valopt component-name "component" "The name of the component, distinct from other installed components"
-valopt package-name "package" "The name of the package, tarball"
-valopt rel-manifest-dir "${CFG_PACKAGE_NAME}lib" "The directory under lib/ where the manifest lives"
-valopt success-message "Installed." "The string to print after successful installation"
-valopt legacy-manifest-dirs "" "Places to look for legacy manifests to uninstall"
-valopt non-installed-overlay "" "Directory containing files that should not be installed"
-valopt bulk-dirs "" "Path prefixes of directories that should be installed/uninstalled in bulk"
-valopt image-dir "./install-image" "The directory containing the installation medium"
-valopt work-dir "./workdir" "The directory to do temporary work"
-valopt output-dir "./dist" "The location to put the final image and tarball"
-
-if [ $HELP -eq 1 ]
-then
- echo
- exit 0
-fi
-
-step_msg "validating arguments"
-validate_opt
-
src_dir="$(abs_path $(dirname "$0"))"
-
-rust_installer_version=`cat "$src_dir/rust-installer-version"`
-
-if [ ! -d "$CFG_IMAGE_DIR" ]
-then
- err "image dir $CFG_IMAGE_DIR does not exist"
-fi
-
-mkdir -p "$CFG_WORK_DIR"
-need_ok "couldn't create work dir"
-
-rm -Rf "$CFG_WORK_DIR/$CFG_PACKAGE_NAME"
-need_ok "couldn't delete work package dir"
-
-mkdir -p "$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$CFG_COMPONENT_NAME"
-need_ok "couldn't create work package dir"
-
-cp -r "$CFG_IMAGE_DIR/"* "$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$CFG_COMPONENT_NAME"
-need_ok "couldn't copy source image"
-
-# Create the manifest
-manifest=`(cd "$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$CFG_COMPONENT_NAME" && find . -type f | sed 's/^\.\///') | sort`
-
-# Remove files in bulk dirs
-bulk_dirs=`echo "$CFG_BULK_DIRS" | tr "," " "`
-for bulk_dir in $bulk_dirs; do
- bulk_dir=`echo "$bulk_dir" | sed s/\\\//\\\\\\\\\\\//g`
- manifest=`echo "$manifest" | sed /^$bulk_dir/d`
-done
-
-# Add 'file:' installation directives, skipping empty lines.
-manifest=`echo "$manifest" | sed /^$/d | sed s/^/file:/`
-
-# Add 'dir:' directives
-for bulk_dir in $bulk_dirs; do
- manifest=`echo "$manifest" && echo "dir:$bulk_dir"`
-done
-
-# The above step may have left a leading empty line if there were only
-# bulk dirs. Remove it.
-manifest=`echo "$manifest" | sed /^$/d`
-
-manifest_file="$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$CFG_COMPONENT_NAME/manifest.in"
-component_file="$CFG_WORK_DIR/$CFG_PACKAGE_NAME/components"
-version_file="$CFG_WORK_DIR/$CFG_PACKAGE_NAME/rust-installer-version"
-
-# Write the manifest
-echo "$manifest" > "$manifest_file"
-
-# Write the component name
-echo "$CFG_COMPONENT_NAME" > "$component_file"
-
-# Write the installer version (only used by combine-installers.sh)
-echo "$rust_installer_version" > "$version_file"
-
-# Copy the overlay
-if [ -n "$CFG_NON_INSTALLED_OVERLAY" ]; then
- overlay_files=`(cd "$CFG_NON_INSTALLED_OVERLAY" && find . -type f)`
- for f in $overlay_files; do
- if [ -e "$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$f" ]; then err "overlay $f exists"; fi
-
- cp "$CFG_NON_INSTALLED_OVERLAY/$f" "$CFG_WORK_DIR/$CFG_PACKAGE_NAME/$f"
- need_ok "failed to copy overlay $f"
- done
-fi
-
-# Generate the install script
-"$src_dir/gen-install-script.sh" \
- --product-name="$CFG_PRODUCT_NAME" \
- --rel-manifest-dir="$CFG_REL_MANIFEST_DIR" \
- --success-message="$CFG_SUCCESS_MESSAGE" \
- --legacy-manifest-dirs="$CFG_LEGACY_MANIFEST_DIRS" \
- --output-script="$CFG_WORK_DIR/$CFG_PACKAGE_NAME/install.sh"
-
-need_ok "failed to generate install script"
-
-mkdir -p "$CFG_OUTPUT_DIR"
-need_ok "couldn't create output dir"
-
-"$src_dir/make-tarballs.sh" \
- --work-dir="$CFG_WORK_DIR" \
- --input="$CFG_PACKAGE_NAME" \
- --output="$CFG_OUTPUT_DIR/$CFG_PACKAGE_NAME"
+cargo run --manifest-path="$src_dir/Cargo.toml" -- generate "$@"
diff --git a/src/generator.rs b/src/generator.rs
new file mode 100644
index 0000000..ff2cf3a
--- /dev/null
+++ b/src/generator.rs
@@ -0,0 +1,211 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::fs;
+use std::io::{self, Write};
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+#[derive(Debug)]
+pub struct Generator {
+ product_name: String,
+ component_name: String,
+ package_name: String,
+ rel_manifest_dir: String,
+ success_message: String,
+ legacy_manifest_dirs: String,
+ non_installed_overlay: String,
+ bulk_dirs: String,
+ image_dir: String,
+ work_dir: String,
+ output_dir: String,
+}
+
+impl Default for Generator {
+ fn default() -> Generator {
+ Generator {
+ product_name: "Product".into(),
+ component_name: "component".into(),
+ package_name: "package".into(),
+ rel_manifest_dir: "packagelib".into(),
+ success_message: "Installed.".into(),
+ legacy_manifest_dirs: "".into(),
+ non_installed_overlay: "".into(),
+ bulk_dirs: "".into(),
+ image_dir: "./install_image".into(),
+ work_dir: "./workdir".into(),
+ output_dir: "./dist".into(),
+ }
+ }
+}
+
+impl Generator {
+ /// The name of the product, for display
+ pub fn product_name(&mut self, value: String) -> &mut Self {
+ self.product_name = value;
+ self
+ }
+
+ /// The name of the component, distinct from other installed components
+ pub fn component_name(&mut self, value: String) -> &mut Self {
+ self.component_name = value;
+ self
+ }
+
+ /// The name of the package, tarball
+ pub fn package_name(&mut self, value: String) -> &mut Self {
+ self.package_name = value;
+ self
+ }
+
+ /// The directory under lib/ where the manifest lives
+ pub fn rel_manifest_dir(&mut self, value: String) -> &mut Self {
+ self.rel_manifest_dir = value;
+ self
+ }
+
+ /// The string to print after successful installation
+ pub fn success_message(&mut self, value: String) -> &mut Self {
+ self.success_message = value;
+ self
+ }
+
+ /// Places to look for legacy manifests to uninstall
+ pub fn legacy_manifest_dirs(&mut self, value: String) -> &mut Self {
+ self.legacy_manifest_dirs = value;
+ self
+ }
+
+ /// Directory containing files that should not be installed
+ pub fn non_installed_overlay(&mut self, value: String) -> &mut Self {
+ self.non_installed_overlay = value;
+ self
+ }
+
+ /// Path prefixes of directories that should be installed/uninstalled in bulk
+ pub fn bulk_dirs(&mut self, value: String) -> &mut Self {
+ self.bulk_dirs = value;
+ self
+ }
+
+ /// The directory containing the installation medium
+ pub fn image_dir(&mut self, value: String) -> &mut Self {
+ self.image_dir = value;
+ self
+ }
+
+ /// The directory to do temporary work
+ pub fn work_dir(&mut self, value: String) -> &mut Self {
+ self.work_dir = value;
+ self
+ }
+
+ /// The location to put the final image and tarball
+ pub fn output_dir(&mut self, value: String) -> &mut Self {
+ self.output_dir = value;
+ self
+ }
+
+ /// Generate the actual installer tarball
+ pub fn run(self) -> io::Result<()> {
+ let src_dir = Path::new(::SOURCE_DIRECTORY);
+ fs::read_dir(&src_dir)?;
+
+ fs::create_dir_all(&self.work_dir)?;
+
+ let package_dir = Path::new(&self.work_dir).join(&self.package_name);
+ if package_dir.exists() {
+ fs::remove_dir_all(&package_dir)?;
+ }
+
+ let component_dir = package_dir.join(&self.component_name);
+ fs::create_dir_all(&component_dir)?;
+ let mut files = cp_r(self.image_dir.as_ref(), &component_dir)?;
+
+ // Filter out files that are covered by bulk dirs.
+ let bulk_dirs: Vec<_> = self.bulk_dirs.split(',').filter(|s| !s.is_empty()).collect();
+ files.retain(|f| !bulk_dirs.iter().any(|d| f.starts_with(d)));
+
+ // Write the manifest
+ let manifest = fs::File::create(component_dir.join("manifest.in"))?;
+ for file in files {
+ writeln!(&manifest, "file:{}", file.display())?;
+ }
+ for dir in bulk_dirs {
+ writeln!(&manifest, "dir:{}", dir)?;
+ }
+ drop(manifest);
+
+ // Write the component name
+ let components = fs::File::create(package_dir.join("components"))?;
+ writeln!(&components, "{}", self.component_name)?;
+ drop(components);
+
+ // Write the installer version (only used by combine-installers.sh)
+ let version = fs::File::create(package_dir.join("rust-installer-version"))?;
+ writeln!(&version, "{}", ::RUST_INSTALLER_VERSION)?;
+ drop(version);
+
+ // Copy the overlay
+ if !self.non_installed_overlay.is_empty() {
+ cp_r(self.non_installed_overlay.as_ref(), &package_dir)?;
+ }
+
+ // Generate the install script (TODO: run this in-process!)
+ let output_script = package_dir.join("install.sh");
+ let status = Command::new(src_dir.join("gen-install-script.sh"))
+ .arg(format!("--product-name={}", self.product_name))
+ .arg(format!("--rel-manifest-dir={}", self.rel_manifest_dir))
+ .arg(format!("--success-message={}", self.success_message))
+ .arg(format!("--legacy-manifest-dirs={}", self.legacy_manifest_dirs))
+ .arg(format!("--output-script={}", output_script.display()))
+ .status()?;
+ if !status.success() {
+ let msg = format!("failed to generate install script: {}", status);
+ return Err(io::Error::new(io::ErrorKind::Other, msg));
+ }
+
+ // Make the tarballs (TODO: run this in-process!)
+ fs::create_dir_all(&self.output_dir)?;
+ let output = Path::new(&self.output_dir).join(&self.package_name);
+ let status = Command::new(src_dir.join("make-tarballs.sh"))
+ .arg(format!("--work-dir={}", self.work_dir))
+ .arg(format!("--input={}", self.package_name))
+ .arg(format!("--output={}", output.display()))
+ .status()?;
+ if !status.success() {
+ let msg = format!("failed to make tarballs: {}", status);
+ return Err(io::Error::new(io::ErrorKind::Other, msg));
+ }
+
+ Ok(())
+ }
+}
+
+/// Copies the `src` directory recursively to `dst`. Both are assumed to exist
+/// when this function is called. Returns a list of files written relative to `dst`.
+pub fn cp_r(src: &Path, dst: &Path) -> io::Result<Vec<PathBuf>> {
+ let mut files = vec![];
+ for f in fs::read_dir(src)? {
+ let f = f?;
+ let path = f.path();
+ let name = PathBuf::from(f.file_name());
+ let dst = dst.join(&name);
+ if f.file_type()?.is_dir() {
+ fs::create_dir(&dst)?;
+ let subfiles = cp_r(&path, &dst)?;
+ files.extend(subfiles.into_iter().map(|f| name.join(f)));
+ } else {
+ fs::copy(&path, &dst)?;
+ files.push(name);
+ }
+ }
+ Ok(files)
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..c81dc12
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,21 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod generator;
+
+pub use generator::Generator;
+
+/// The base source directory
+/// (FIXME: statically compiling this means we can't ship installer binaries!)
+const SOURCE_DIRECTORY: &'static str = env!("CARGO_MANIFEST_DIR");
+
+/// The installer version, output only to be used by combine-installers.sh.
+/// (should match `SOURCE_DIRECTORY/rust_installer_version`)
+pub const RUST_INSTALLER_VERSION: u32 = 3;
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..bdc2dd5
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,58 @@
+#[macro_use]
+extern crate clap;
+extern crate installer;
+
+use clap::{App, ArgMatches};
+use installer::Generator;
+
+fn main() {
+ let yaml = load_yaml!("main.yml");
+ let matches = App::from_yaml(yaml).get_matches();
+
+ match matches.subcommand() {
+ ("generate", Some(matches)) => generate(matches),
+ _ => unreachable!(),
+ }
+}
+
+fn generate(matches: &ArgMatches) {
+ let mut gen = Generator::default();
+ matches
+ .value_of("product-name")
+ .map(|s| gen.product_name(s.into()));
+ matches
+ .value_of("component-name")
+ .map(|s| gen.component_name(s.into()));
+ matches
+ .value_of("package-name")
+ .map(|s| gen.package_name(s.into()));
+ matches
+ .value_of("rel-manifest-dir")
+ .map(|s| gen.rel_manifest_dir(s.into()));
+ matches
+ .value_of("success-message")
+ .map(|s| gen.success_message(s.into()));
+ matches
+ .value_of("legacy-manifest-dirs")
+ .map(|s| gen.legacy_manifest_dirs(s.into()));
+ matches
+ .value_of("non-installed-overlay")
+ .map(|s| gen.non_installed_overlay(s.into()));
+ matches
+ .value_of("bulk-dirs")
+ .map(|s| gen.bulk_dirs(s.into()));
+ matches
+ .value_of("image-dir")
+ .map(|s| gen.image_dir(s.into()));
+ matches
+ .value_of("work-dir")
+ .map(|s| gen.work_dir(s.into()));
+ matches
+ .value_of("output-dir")
+ .map(|s| gen.output_dir(s.into()));
+
+ if let Err(e) = gen.run() {
+ println!("failed to generate installer: {}", e);
+ std::process::exit(1);
+ }
+}
diff --git a/src/main.yml b/src/main.yml
new file mode 100644
index 0000000..ead98b6
--- /dev/null
+++ b/src/main.yml
@@ -0,0 +1,62 @@
+name: installer
+settings:
+ - ArgRequiredElseHelp
+subcommands:
+ - generate:
+ args:
+ - product-name:
+ help: The name of the product, for display
+ long: product-name
+ takes_value: true
+ value_name: NAME
+ - component-name:
+ help: The name of the component, distinct from other installed components
+ long: component-name
+ takes_value: true
+ value_name: NAME
+ - package-name:
+ help: The name of the package, tarball
+ long: package-name
+ takes_value: true
+ value_name: NAME
+ - rel-manifest-dir:
+ help: The directory under lib/ where the manifest lives
+ long: rel-manifest-dir
+ takes_value: true
+ value_name: DIR
+ - success-message:
+ help: The string to print after successful installation
+ long: success-message
+ takes_value: true
+ value_name: MESSAGE
+ - legacy-manifest-dirs:
+ help: Places to look for legacy manifests to uninstall
+ long: legacy-manifest-dirs
+ takes_value: true
+ value_name: DIRS
+ - non-installed-overlay:
+ help: Directory containing files that should not be installed
+ long: non-installed-overlay
+ takes_value: true
+ value_name: DIR
+ - bulk-dirs:
+ help: Path prefixes of directories that should be installed/uninstalled in bulk
+ long: bulk-dirs
+ takes_value: true
+ value_name: DIRS
+ - image-dir:
+ help: The directory containing the installation medium
+ long: image-dir
+ takes_value: true
+ value_name: DIR
+ - work-dir:
+ help: The directory to do temporary work
+ long: work-dir
+ takes_value: true
+ value_name: DIR
+ - output-dir:
+ help: The location to put the final image and tarball
+ long: output-dir
+ takes_value: true
+ value_name: DIR
+