use crate::config::{downloads_directory, install_directory, INKO_EXE};
use crate::error::Error;
use crate::http;
use crate::manifest::Manifest;
use crate::version::Version;
use dircpy::CopyBuilder;
use flate2::read::GzDecoder;
use getopts::Options;
use std::fs::{copy, create_dir, create_dir_all, remove_dir_all};
use std::path::PathBuf;
use std::process::Command;
use tar::Archive;
const USAGE: &str = "ivm install [OPTIONS] [VERSION]
Installs a new version.
Examples:
ivm install 0.8.0 # Installs version 0.8.0
ivm install latest # Installs the latest available version";
pub fn run(arguments: &[String]) -> Result<(), Error> {
let mut options = Options::new();
options.optflag("h", "help", "Shows this help message");
let matches = options.parse(arguments)?;
if matches.opt_present("h") {
usage!(options, USAGE);
return Ok(());
}
Manifest::refresh()?;
let manifest = Manifest::parse()?;
let version = match matches.free.get(0).map(|s| s.as_str()) {
Some("latest") => manifest.latest()?,
Some(version) => Version::parse(version)?,
None => {
return Err(Error::generic(
"You must specify a version to install",
));
}
};
info!("Downloading version {}", version);
let source = extract(&version)?;
let target = install_directory()?.join(version.to_string());
info!("Installing version {}", version);
install(&source, &target)?;
info!("Removing source directory");
remove_dir_all(source).map_err(|error| {
Error::generic(format!(
"Failed to remove the source directory: {}",
error
))
})?;
info!("Version {} has been installed", version);
Ok(())
}
fn extract(version: &Version) -> Result<PathBuf, Error> {
let url = &format!("https://releases.inko-lang.org/{}.tar.gz", version);
let extract_to = downloads_directory()?.join(version.to_string());
if extract_to.exists() {
return Ok(extract_to);
}
if !http::exists(url) {
return Err(Error::generic("The version does not exist"));
}
let response = http::get(url)?.into_reader();
create_dir(&extract_to).map_err(|error| {
Error::generic(format!(
"Failed to create {}: {}",
extract_to.to_string_lossy(),
error
))
})?;
Archive::new(GzDecoder::new(response))
.entries()
.and_then(|entries| {
for entry in entries {
entry.and_then(|mut entry| entry.unpack_in(&extract_to))?;
}
Ok(())
})
.map_err(|error| {
Error::generic(format!(
"Failed to unpack the TAR archive into {}: {}",
extract_to.to_string_lossy(),
error
))
})?;
Ok(extract_to)
}
fn install(source: &PathBuf, target: &PathBuf) -> Result<(), Error> {
if target.is_dir() {
return Err(Error::generic("The version is already installed"));
}
let bin_dir = target.join("bin");
let compiler_dir = target.join("compiler");
let runtime_dir = target.join("runtime");
let compiler_bin_dir = compiler_dir.join("bin");
let compiler_lib_dir = compiler_dir.join("lib");
let compiler_bin = compiler_bin_dir.join("inkoc");
let mut command = Command::new("cargo");
command
.arg("build")
.arg("--release")
.env("RUSTFLAGS", "-C target-feature=+aes")
.env("INKO_COMPILER_BIN", &compiler_bin)
.env("INKO_COMPILER_LIB", &compiler_lib_dir)
.env("INKO_RUNTIME_LIB", &runtime_dir)
.current_dir(&source.join("cli"));
if !cfg!(windows) {
command.arg("--features").arg("libinko/libffi-system");
}
run_command(&mut command)?;
mkdir_p(&compiler_bin_dir)?;
mkdir_p(&compiler_lib_dir)?;
mkdir_p(&runtime_dir)?;
mkdir_p(&bin_dir)?;
cp(
source.join("target/release").join(INKO_EXE),
bin_dir.join(INKO_EXE),
)?;
cp(source.join("compiler/bin/inkoc"), compiler_bin)?;
cp(source.join("LICENSE"), target.join("LICENSE"))?;
cp_r(source.join("compiler/lib"), compiler_lib_dir)?;
cp_r(source.join("runtime/src"), runtime_dir)?;
Ok(())
}
fn run_command(command: &mut Command) -> Result<(), Error> {
command
.spawn()
.and_then(|mut child| {
child.wait().map(|status| status.code().unwrap_or(0))
})
.map_err(|e| e.into())
.and_then(|status| {
if status == 0 {
Ok(())
} else {
Err(Error::generic(format!(
"The command exited with status code {}",
status
)))
}
})
}
fn cp_r(source: PathBuf, target: PathBuf) -> Result<(), Error> {
CopyBuilder::new(&source, &target)
.overwrite(true)
.run()
.map_err(|error| {
Error::generic(format!(
"Failed to copy {} to {}: {}",
source.to_string_lossy(),
target.to_string_lossy(),
error
))
})
}
fn cp(source: PathBuf, target: PathBuf) -> Result<(), Error> {
copy(&source, &target).map_err(|error| {
Error::generic(format!(
"Failed to copy {} to {}: {}",
source.to_string_lossy(),
target.to_string_lossy(),
error
))
})?;
Ok(())
}
fn mkdir_p(path: &PathBuf) -> Result<(), Error> {
create_dir_all(&path).map_err(|error| {
Error::generic(format!(
"Failed to create the directory {}: {}",
path.to_string_lossy(),
error
))
})
}