Bundles are for nerds

This commit is contained in:
Francesca Plebani
2020-02-27 17:38:04 -08:00
parent 4d697dc45a
commit 67a5bf9fd7
15 changed files with 0 additions and 851 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
/target
**/*.rs.bk
.DS_Store
/bundles

View File

@@ -1,9 +1,7 @@
[workspace]
members = [
"ginit",
"ginit-bundle",
"ginit-core",
"ginit-install",
"ginit-macros",
"ginit-os",
"plugins/ginit-android",

View File

@@ -1,16 +0,0 @@
[package]
name = "ginit-bundle"
version = "0.1.0"
authors = ["Francesca Plebani <francesca@brainiumstudios.com>"]
edition = "2018"
description = "Generate distributable bundles for ginit or plugins"
[dependencies]
ginit-core = { path = "../ginit-core" }
log = "0.4.8"
structopt = "0.3.7"
zip = { version = "0.5.4", features = ["time"] }
[build-dependencies]
serde = { version = "1.0.101", features = ["derive"] }
toml = "0.5.5"

View File

@@ -1,9 +0,0 @@
# `ginit-bundle`
This tool can be used for 2 things (wow!):
1. Packaging ginit
2. Packaging plugins
Which of those behaviors to take is determined by the current directory. If you're in ginit's manifest root, it'll package ginit... and if you're in a plugin's manifest root, it'll package the plugin! Crazy stuff.
By default, bundles will end up in a `bundles` folder within said manifest root. Of course, Francesca's so lovely that `ginit-bundle` will inform you of the location anyway...

View File

@@ -1,27 +0,0 @@
use serde::Deserialize;
use std::{env, fs, path::PathBuf};
#[derive(Debug, Deserialize)]
struct Package {
version: String,
}
#[derive(Debug, Deserialize)]
struct CargoToml {
package: Package,
}
fn main() {
let CargoToml {
package: Package { version },
} = {
let cargo_toml_path =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../ginit/Cargo.toml");
println!("cargo:rerun-if-changed={}", cargo_toml_path.display());
let bytes = fs::read(cargo_toml_path).expect("failed to read ginit's Cargo.toml");
toml::from_slice::<CargoToml>(&bytes).expect("failed to parse ginit's Cargo.toml")
};
let content = format!("static VERSION: &'static str = {:?};\n", version);
let version_rs_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("version.rs");
fs::write(version_rs_path, &content).expect("failed to write version.rs");
}

View File

@@ -1,103 +0,0 @@
use std::{
fmt::{self, Display},
fs::{self, File},
io::{self, Write},
path::{Path, PathBuf},
};
use zip::{result::ZipError, write::FileOptions, ZipWriter};
#[derive(Debug)]
pub enum Error {
CreateFailed { tried: PathBuf, cause: io::Error },
DirReadFailed { tried: PathBuf, cause: io::Error },
DirEntryFailed { tried: PathBuf, cause: io::Error },
AddFailed { tried: PathBuf, cause: ZipError },
ReadFailed { tried: PathBuf, cause: io::Error },
WriteFailed { tried: PathBuf, cause: io::Error },
FinishFailed(ZipError),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CreateFailed { tried, cause } => {
write!(f, "Failed to create zip file {:?}: {}", tried, cause)
}
Self::DirReadFailed { tried, cause } => {
write!(f, "Failed to read bundle directory {:?}: {}", tried, cause)
}
Self::DirEntryFailed { tried, cause } => write!(
f,
"Failed to get entry in bundle directory {:?}: {}",
tried, cause
),
Self::AddFailed { tried, cause } => {
write!(f, "Failed to add to zip file {:?}: {}", tried, cause)
}
Self::ReadFailed { tried, cause } => {
write!(f, "Failed to read bundle file {:?}: {}", tried, cause)
}
Self::WriteFailed { tried, cause } => write!(
f,
"Failed to write contents of bundle file {:?} to zip: {}",
tried, cause
),
Self::FinishFailed(err) => write!(f, "Failed to finish zip: {}", err),
}
}
}
fn traverse(
writer: &mut ZipWriter<&mut File>,
options: FileOptions,
dir: &Path,
prefix: &Path,
) -> Result<(), Error> {
for entry in fs::read_dir(dir).map_err(|cause| Error::DirReadFailed {
tried: dir.to_owned(),
cause,
})? {
let entry = entry.map_err(|cause| Error::DirEntryFailed {
tried: dir.to_owned(),
cause,
})?;
let path = entry.path();
let name = path.strip_prefix(prefix).unwrap();
if path.is_file() {
log::info!("adding file {:?} to archive as {:?}", path, name);
writer
.start_file_from_path(name, options)
.map_err(|cause| Error::AddFailed {
tried: name.to_owned(),
cause,
})?;
let bytes = fs::read(&path).map_err(|cause| Error::ReadFailed {
tried: path.to_owned(),
cause,
})?;
writer
.write_all(&bytes)
.map_err(|cause| Error::WriteFailed {
tried: path.to_owned(),
cause,
})?;
} else if name.as_os_str().len() != 0 {
log::info!("adding directory {:?} to archive as {:?}", path, name);
traverse(writer, options, &path, prefix)?;
}
}
Ok(())
}
pub fn zip(bundle_root: &Path, bundle_path: &Path) -> Result<PathBuf, Error> {
let zip_path = PathBuf::from(format!("{}.zip", bundle_path.display()));
let mut file = File::create(&zip_path).map_err(|cause| Error::CreateFailed {
tried: zip_path.clone(),
cause,
})?;
let mut writer = ZipWriter::new(&mut file);
let options = FileOptions::default();
traverse(&mut writer, options, bundle_path, bundle_root)?;
writer.finish().map_err(Error::FinishFailed)?;
Ok(zip_path)
}

View File

@@ -1,129 +0,0 @@
mod archive;
mod package;
mod plugin;
use self::{package::Package, plugin::Plugin};
use ginit_core::{
bundle::manifest,
config::empty,
util::{self, cli},
};
use std::{
env,
fmt::{self, Display},
io,
path::PathBuf,
};
use structopt::StructOpt;
#[derive(Debug)]
enum Error {
CurrentDirFailed(io::Error),
PluginLoadFailed(manifest::Error),
BundleFailed(package::Error),
ArchiveFailed(archive::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CurrentDirFailed(err) => {
write!(f, "Failed to get current working directory: {}", err)
}
Self::PluginLoadFailed(err) => write!(f, "Failed to read plugin manifest: {}", err),
Self::BundleFailed(err) => write!(f, "Failed to create bundle: {}", err),
Self::ArchiveFailed(err) => write!(f, "Failed to archive bundle: {}", err),
}
}
}
#[cli::main(env!("CARGO_PKG_NAME"))]
#[derive(Debug, StructOpt)]
#[structopt(
author = env!("CARGO_PKG_AUTHORS"),
about = env!("CARGO_PKG_DESCRIPTION"),
settings = cli::GLOBAL_SETTINGS,
)]
struct Input {
#[structopt(flatten)]
flags: cli::GlobalFlags,
#[structopt(
short = "i",
long = "manifest-root",
about = "Specify manifest root instead of using the current directory"
)]
manifest_root: Option<PathBuf>,
#[structopt(
short = "o",
long = "bundle-root",
about = "Specify bundle root instead of using the manifest root"
)]
bundle_root: Option<PathBuf>,
#[structopt(flatten)]
profile: cli::Profile,
#[structopt(long = "zip", about = "Zip up bundle, for your utmost convenience")]
zip: bool,
}
impl cli::Exec for Input {
type Config = empty::Config;
type Error = Error;
fn global_flags(&self) -> cli::GlobalFlags {
self.flags
}
fn exec(
self,
_config: Option<Self::Config>,
_wrapper: &util::TextWrapper,
) -> Result<(), Self::Error> {
let Self {
manifest_root,
bundle_root,
profile: cli::Profile { profile },
zip,
..
} = self;
let manifest_root = match manifest_root {
Some(manifest_root) => manifest_root,
None => env::current_dir().map_err(Error::CurrentDirFailed)?,
};
let bundle_root = match bundle_root {
Some(bundle_root) => bundle_root,
None => manifest_root.join("bundles"),
};
log::info!("using manifest root {:?}", manifest_root);
log::info!("using bundle root {:?}", bundle_root);
let is_ginit = manifest_root.join("ginit").exists()
&& manifest_root.join("ginit-bundle").exists()
&& manifest_root.join("ginit-core").exists()
&& manifest_root.join("ginit-install").exists()
&& manifest_root.join("ginit-macros").exists()
&& manifest_root.join("ginit-os").exists();
let package = if is_ginit {
log::info!("detected that package is ginit");
Package::Ginit
} else {
log::info!("detected that package is a plugin");
let plugin = Plugin::load(&manifest_root).map_err(Error::PluginLoadFailed)?;
Package::Plugin(plugin)
};
let bundle_path = package
.bundle(&manifest_root, &bundle_root, profile)
.map_err(Error::BundleFailed)?;
println!("Created bundle successfully! {}", bundle_path.display());
if zip {
let zip_path =
archive::zip(&bundle_root, &bundle_path).map_err(Error::ArchiveFailed)?;
println!("Archived bundle successfully! {}", zip_path.display());
}
Ok(())
}
}

View File

@@ -1,338 +0,0 @@
use super::plugin::Plugin;
use ginit_core::{
bundle::{global_config::GlobalConfig, manifest},
exports::{
into_result::{command::CommandError, IntoResult as _},
toml,
},
opts, os,
util::CargoCommand,
};
use std::{
fmt::{self, Display},
fs, io,
path::{Path, PathBuf},
process::Command,
};
include!(concat!(env!("OUT_DIR"), "/version.rs"));
#[derive(Debug)]
pub enum Error {
BundleDirCreationFailed {
package: String,
tried: PathBuf,
cause: io::Error,
},
BinBuildFailed {
package: String,
cause: CommandError,
},
BinCopyFailed {
package: String,
src: PathBuf,
dest: PathBuf,
cause: io::Error,
},
ManifestSerializeFailed {
package: String,
cause: toml::ser::Error,
},
ManifestWriteFailed {
package: String,
dest: PathBuf,
cause: io::Error,
},
TemplatesCopyFailed {
package: String,
src: PathBuf,
dest: PathBuf,
cause: CommandError,
},
PluginsDirReadFailed {
package: String,
tried: PathBuf,
cause: io::Error,
},
PluginsDirEntryFailed {
package: String,
tried: PathBuf,
cause: io::Error,
},
ManifestReadFailed {
package: String,
tried: PathBuf,
cause: manifest::Error,
},
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BundleDirCreationFailed {
package,
tried,
cause,
} => write!(
f,
"Failed to create bundle directory at {:?} for package {:?}: {}",
package, tried, cause
),
Self::BinBuildFailed { package, cause } => write!(
f,
"Failed to build binary for package {:?}: {}",
package, cause
),
Self::BinCopyFailed {
package,
src,
dest,
cause,
} => write!(
f,
"Failed to copy binary for package {:?} from {:?} to {:?}: {}",
package, src, dest, cause
),
Self::ManifestSerializeFailed { package, cause } => write!(
f,
"Failed to serialize manifest for package {:?}: {}",
package, cause
),
Self::ManifestWriteFailed {
package,
dest,
cause,
} => write!(
f,
"Failed to write manifest to {:?} for package {:?}: {}",
package, dest, cause
),
Self::TemplatesCopyFailed {
package,
src,
dest,
cause,
} => write!(
f,
"Failed to copy templates for package {:?} from {:?} to {:?}: {}",
package, src, dest, cause
),
Self::PluginsDirReadFailed {
package,
tried,
cause,
} => write!(
f,
"Failed to read plugins directory at {:?} for package {:?}: {}",
package, tried, cause
),
Self::PluginsDirEntryFailed {
package,
tried,
cause,
} => write!(
f,
"Failed to read plugins directory entry at {:?} for package {:?}: {}",
package, tried, cause
),
Self::ManifestReadFailed {
package,
tried,
cause,
} => write!(
f,
"Failed to read bundled plugin manifest at {:?} for package {:?}: {}",
package, tried, cause
),
}
}
}
#[derive(Debug)]
pub enum Package {
Ginit,
BundledPlugin(Plugin),
Plugin(Plugin),
}
impl Package {
fn as_str(&self) -> &str {
match self {
Self::Ginit => ginit_core::NAME,
Self::BundledPlugin(plugin) | Self::Plugin(plugin) => plugin.manifest.full_name(),
}
}
fn bin_name(&self) -> PathBuf {
match self {
Self::Ginit => os::add_ext_to_bin_name(format!("cargo-{}", self.as_str())),
Self::BundledPlugin(_) | Self::Plugin(_) => os::add_ext_to_bin_name(self.as_str()),
}
}
pub fn bundle_name(&self, profile: opts::Profile) -> String {
let (name, version) = match self {
Self::Ginit | Self::BundledPlugin(_) => (ginit_core::NAME, VERSION),
Self::Plugin(plugin) => (self.as_str(), plugin.manifest.version()),
};
format!("{}-{}-{}-{}", name, version, profile.as_str(), os::NAME)
}
fn bundle_base(&self, bundle_root: &Path, profile: opts::Profile) -> PathBuf {
match self {
Self::Ginit | Self::Plugin(_) => bundle_root.join(self.bundle_name(profile)),
Self::BundledPlugin(plugin) => Self::bundle_base(&Self::Ginit, bundle_root, profile)
.join("plugins")
.join(plugin.manifest.full_name()),
}
}
fn build_bin(&self, manifest_root: &Path, profile: opts::Profile) -> Result<(), Error> {
CargoCommand::new("build")
.with_package(Some(self.as_str()))
.with_manifest_path(Some(manifest_root.join("Cargo.toml")))
.with_release(profile.is_release())
.into_command_impure()
.status()
.into_result()
.map_err(|cause| Error::BinBuildFailed {
package: self.as_str().to_owned(),
cause,
})
}
fn copy_bin(
&self,
manifest_root: &Path,
bundle_root: &Path,
profile: opts::Profile,
) -> Result<(), Error> {
let bin = self.bin_name();
let src = manifest_root
.join("target")
.join(profile.as_str())
.join(&bin);
let dest = self.bundle_base(bundle_root, profile).join(bin);
fs::copy(&src, &dest).map_err(|cause| Error::BinCopyFailed {
package: self.as_str().to_owned(),
src,
dest,
cause,
})?;
Ok(())
}
fn write_manifest(&self, bundle_root: &Path, profile: opts::Profile) -> Result<(), Error> {
if let Self::BundledPlugin(plugin) | Self::Plugin(plugin) = self {
let dest = self
.bundle_base(bundle_root, profile)
.join(format!("{}.toml", plugin.manifest.full_name()));
let ser = toml::to_string_pretty(&plugin.manifest).map_err(|cause| {
Error::ManifestSerializeFailed {
package: self.as_str().to_owned(),
cause,
}
})?;
fs::write(&dest, &ser).map_err(|cause| Error::ManifestWriteFailed {
package: self.as_str().to_owned(),
dest,
cause,
})
} else {
// TODO: this entire branch feels a bit silly
let dest = self
.bundle_base(bundle_root, profile)
.join(format!("global-config.toml"));
let global_config = GlobalConfig {
default_plugins: vec![
"brainium".to_owned(),
"android".to_owned(),
"ios".to_owned(),
],
};
let ser = toml::to_string_pretty(&global_config).map_err(|cause| {
Error::ManifestSerializeFailed {
package: self.as_str().to_owned(),
cause,
}
})?;
fs::write(&dest, &ser).map_err(|cause| Error::ManifestWriteFailed {
package: self.as_str().to_owned(),
dest,
cause,
})
}
}
fn copy_templates(
&self,
manifest_root: &Path,
bundle_root: &Path,
profile: opts::Profile,
) -> Result<(), Error> {
let src = match self {
Self::Ginit => manifest_root.join("templates"),
Self::BundledPlugin(plugin) | Self::Plugin(plugin) => plugin.dir.join("templates"),
};
if src.exists() {
let dest = self.bundle_base(bundle_root, profile);
Command::new("cp")
.arg("-rp")
.args(&[&src, &dest])
.status()
.into_result()
.map_err(|cause| Error::TemplatesCopyFailed {
package: self.as_str().to_owned(),
src,
dest,
cause,
})
} else {
Ok(())
}
}
pub fn bundle(
&self,
manifest_root: &Path,
bundle_root: &Path,
profile: opts::Profile,
) -> Result<PathBuf, Error> {
let bundle_base = self.bundle_base(bundle_root, profile);
fs::create_dir_all(&bundle_base).map_err(|cause| Error::BundleDirCreationFailed {
package: self.as_str().to_owned(),
tried: bundle_base.clone(),
cause,
})?;
self.build_bin(manifest_root, profile)?;
self.copy_bin(manifest_root, bundle_root, profile)?;
self.write_manifest(bundle_root, profile)?;
self.copy_templates(manifest_root, bundle_root, profile)?;
if let Self::Ginit = self {
// TODO: include ginit-bundle and ginit-install!
let plugins_dir = manifest_root.join("plugins");
for entry in
fs::read_dir(&plugins_dir).map_err(|cause| Error::PluginsDirReadFailed {
package: self.as_str().to_owned(),
tried: plugins_dir.clone(),
cause,
})?
{
let entry = entry.map_err(|cause| Error::PluginsDirEntryFailed {
package: self.as_str().to_owned(),
tried: plugins_dir.clone(),
cause,
})?;
let plugin =
Plugin::load(entry.path()).map_err(|cause| Error::ManifestReadFailed {
package: self.as_str().to_owned(),
tried: entry.path().clone(),
cause,
})?;
let package = Package::BundledPlugin(plugin);
package.bundle(manifest_root, bundle_root, profile)?;
}
}
Ok(bundle_base)
}
}

View File

@@ -1,16 +0,0 @@
use ginit_core::bundle::manifest::{self, Manifest};
use std::path::PathBuf;
#[derive(Debug)]
pub struct Plugin {
pub dir: PathBuf,
pub manifest: Manifest,
}
impl Plugin {
pub fn load(dir: impl Into<PathBuf>) -> Result<Self, manifest::Error> {
let dir = dir.into();
Manifest::load_from_cargo_toml(dir.join("Cargo.toml"))
.map(|manifest| Self { dir, manifest })
}
}

View File

@@ -1,13 +0,0 @@
[package]
name = "ginit-install"
version = "0.1.0"
authors = ["Francesca Plebani <francesca@brainiumstudios.com>"]
edition = "2018"
[dependencies]
ginit-core = { path = "../ginit-core" }
structopt = "0.3.7"
[build-dependencies]
serde = { version = "1.0.101", features = ["derive"] }
toml = "0.5.5"

View File

@@ -1,27 +0,0 @@
use serde::Deserialize;
use std::{env, fs, path::PathBuf};
#[derive(Debug, Deserialize)]
struct Package {
version: String,
}
#[derive(Debug, Deserialize)]
struct CargoToml {
package: Package,
}
fn main() {
let CargoToml {
package: Package { version },
} = {
let cargo_toml_path =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../ginit/Cargo.toml");
println!("cargo:rerun-if-changed={}", cargo_toml_path.display());
let bytes = fs::read(cargo_toml_path).expect("failed to read ginit's Cargo.toml");
toml::from_slice::<CargoToml>(&bytes).expect("failed to parse ginit's Cargo.toml")
};
let content = format!("pub static VERSION: &'static str = {:?};\n", version);
let version_rs_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("version.rs");
fs::write(version_rs_path, &content).expect("failed to write version.rs");
}

View File

@@ -1,71 +0,0 @@
use ginit_core::{
exports::into_result::{
command::{CommandError, CommandResult},
IntoResult as _,
},
opts, os,
util::CargoCommand,
};
use std::{fs, io, path::Path, process::Command};
fn bundle_name() -> String {
include!(concat!(env!("OUT_DIR"), "/version.rs"));
format!("{}-{}-{}", ginit_core::NAME, VERSION, os::NAME)
}
#[derive(Debug)]
pub enum Package<'a> {
Ginit,
Plugin(&'a str),
}
impl<'a> Package<'a> {
fn as_str(&self) -> &'a str {
match self {
Self::Ginit => "ginit",
Self::Plugin(name) => name,
}
}
fn bundle_base(&self, root: &Path) -> PathBuf {
match self {
Self::Ginit => root.join("bundles").join(bundle_name()),
Self::Plugin(name) => Self::bundle_base(&Self::Ginit, root)
.join("plugins")
.join(name),
}
}
fn build_bin(&self, root: &Path, profile: opts::Profile) -> CommandResult<()> {
CargoCommand::new("build")
.with_package(Some(self.as_str()))
.with_manifest_path(root.join("Cargo.toml"))
.with_release(profile.is_release())
.into_command_impure()
.status()
.into_result()
}
fn copy_bin(&self, root: &Path, profile: opts::Profile) -> io::Result<()> {
let bin = os::add_ext_to_bin_name(self.as_str());
let src = root.join("target").join(profile.as_str()).join(&bin);
let dest = self.bundle_base(root).join("bin").join(os::NAME).join(bin);
fs::copy(src, dest)
}
}
// (bin, global_config, templates, plugins)
// bin = [macos, windows, linux]
// plugins = [(bin, manifest, templates)]
pub fn bundle(root: impl AsRef<Path>, profile: opts::Profile) -> io::Result<()> {
// build bin
let result = build_package(root, "ginit", profile);
copy_package(root, "ginit", profile)?;
for entry in fs::read_dir("plugins")? {
let entry = entry?;
let package = entry.path().file_name().unwrap();
let result = build_package(root, package, profile);
copy_package(root, package, profile)?;
}
}

View File

@@ -1,42 +0,0 @@
mod plugin;
use ginit_core::{
dot,
exports::{bicycle, toml},
util::cli::NonZeroExit,
};
use std::{
fs,
path::{Path, PathBuf},
};
fn create_dirs(dot_files: &bundle::Files) -> Result<(), NonZeroExit> {
fs::create_dir_all(dot_files.plugins()).map_err(NonZeroExit::display)
}
fn install_bin(dot_files: &bundle::Files) -> Result<(), NonZeroExit> {
let dest = dot_files.bin();
todo!()
}
fn write_global_config(dot_files: &bundle::Files) -> Result<(), NonZeroExit> {
let ser = toml::to_string_pretty(&bundle::GlobalConfig {
default_plugins: vec![
"brainium".to_owned(),
"android".to_owned(),
"ios".to_owned(),
],
})
.map_err(NonZeroExit::display)?;
fs::write(dot_files.global_config(), ser.as_bytes()).map_err(NonZeroExit::display)
}
fn main() {
NonZeroExit::main(|_wrapper| {
let dot_files = bundle::Files::new().map_err(NonZeroExit::display)?;
create_dirs(&dot_files)?;
install_bin(&dot_files)?;
write_global_config(&dot_files)?;
Ok(())
})
}

View File

@@ -1,56 +0,0 @@
use ginit_core::{
bundle,
exports::{bicycle, toml},
util::{self, cli::NonZeroExit},
};
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Debug)]
pub struct Context<'a> {
bundle_files: &'a bundle::Files,
name: &'a str,
src_dir: PathBuf,
}
impl<'a> Context<'a> {
pub fn new(bundle_files: &'a bundle::Files, name: &'a str) -> Result<Self, NonZeroExit> {
let src_dir = util::temp_dir().join("plugin-src").join(name);
Ok(Self {
bundle_files,
name,
src_dir,
})
}
fn install_bin(&self) -> Result<(), NonZeroExit> {
let dest = self.bundle_files.plugin_bin(self.name);
todo!()
}
fn write_manifest(&self) -> Result<(), NonZeroExit> {
let dest = self.bundle_files.plugin_manifest(self.name);
todo!()
}
fn copy_templates(&self) -> Result<(), NonZeroExit> {
let src = self.src_dir.join("templates");
let dest = self.bundle_files.plugin_templates(self.name);
let actions = bicycle::traverse(src, dest, bicycle::no_transform, None)
.map_err(NonZeroExit::display)?;
bicycle::Bicycle::default()
.process_actions(actions.iter(), |_| ())
.map_err(NonZeroExit::display)
}
pub fn install(self) -> Result<(), NonZeroExit> {
let dest_dir = self.bundle_files.plugin_dir(self.name);
fs::create_dir_all(dest_dir).map_err(NonZeroExit::display)?;
self.install_bin()?;
self.write_manifest()?;
self.copy_templates()?;
fs::remove_dir_all(self.src_dir).map_err(NonZeroExit::display)
}
}

View File

@@ -1 +0,0 @@
pub static VERSION: &'static str = "0.5.0";