Skip to content

Commit

Permalink
feat: add support for working with many plugins and custom assets
Browse files Browse the repository at this point in the history
  • Loading branch information
dodokek authored and lowitea committed Mar 5, 2025
1 parent 3bd6962 commit 98f7ac8
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 72 deletions.
14 changes: 14 additions & 0 deletions plugin_template/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
use pike::helpers::build;

// Custom build script for plugin, which stores
// plugin's artefacts in corresponding folder
//
// Call of build::main() function is MANDATORY
// for proper artefact storage and packing

fn main() {
// In case you want to store custom files in `assets` folder,
// params could be initialised like
//
// let params = build::ParamsBuilder::default()
// .custom_assets(vec!["path_to_file.txt", "another_file.txt"])...
//
// The path is calculated from plugin directory

let params = build::ParamsBuilder::default().build().unwrap();
build::main(&params);
}
128 changes: 91 additions & 37 deletions src/commands/plugin/pack.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use flate2::write::GzEncoder;
use flate2::Compression;
use lib::{cargo_build, BuildType};
use serde::Deserialize;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::{env, fs};
use tar::Builder;
use toml::Value;

use crate::commands::lib;

Expand All @@ -30,11 +30,6 @@ const LIB_EXT: &str = "dylib";

pub fn cmd(pack_debug: bool, target_dir: &PathBuf, pluging_path: &PathBuf) -> Result<()> {
let root_dir = env::current_dir()?.join(pluging_path);
let plugin_name = &root_dir
.file_name()
.context("extracting project name")?
.to_str()
.context("parsing filename to string")?;

let build_dir = if pack_debug {
cargo_build(BuildType::Debug, target_dir, pluging_path)
Expand All @@ -46,24 +41,41 @@ pub fn cmd(pack_debug: bool, target_dir: &PathBuf, pluging_path: &PathBuf) -> Re
Path::new(&root_dir).join(target_dir).join("release")
};

let mut manifest_dir = root_dir.clone();
// Workaround for case, when plugins is a subcrate of workspace
{
let cargo_toml_file: File = File::open(root_dir.join("Cargo.toml")).unwrap();
let toml_reader = BufReader::new(cargo_toml_file);

for line in toml_reader.lines() {
let line = line?;
if line.contains("workspace") {
manifest_dir = root_dir.join(plugin_name);
break;
let plugin_dir = root_dir.clone();

let cargo_toml_path = Path::new("Cargo.toml");
let cargo_toml_content =
fs::read_to_string(cargo_toml_path).expect("Failed to read Cargo.toml");

let parsed_toml: Value = cargo_toml_content
.parse()
.context("Failed to parse Cargo.toml")?;

if let Some(workspace) = parsed_toml.get("workspace") {
if let Some(members) = workspace.get("members") {
if let Some(members_array) = members.as_array() {
for member in members_array {
if let Some(member_str) = member.as_str() {
create_plugin_archive(&build_dir, &root_dir.join(member_str))?;
}
}
}
}

return Ok(());
}

create_plugin_archive(&build_dir, &plugin_dir)
}

fn create_plugin_archive(build_dir: &Path, plugin_dir: &Path) -> Result<()> {
let plugin_version = get_latest_plugin_version(plugin_dir)?;
let plugin_build_dir = build_dir
.join(plugin_dir.file_name().unwrap())
.join(plugin_version);

let cargo_manifest: CargoManifest = toml::from_str(
&fs::read_to_string(manifest_dir.join("Cargo.toml"))
.context("failed to read Cargo.toml")?,
&fs::read_to_string(plugin_dir.join("Cargo.toml")).context("failed to read Cargo.toml")?,
)
.context("failed to parse Cargo.toml")?;

Expand All @@ -80,30 +92,72 @@ pub fn cmd(pack_debug: bool, target_dir: &PathBuf, pluging_path: &PathBuf) -> Re
let mut encoder = GzEncoder::new(compressed_file, Compression::best());

let lib_name = format!("lib{normalized_package_name}.{LIB_EXT}");
let mut lib_file =
File::open(build_dir.join(&lib_name)).context(format!("failed to open {lib_name}"))?;

let mut manifest_file =
File::open(build_dir.join("manifest.yaml")).context("failed to open file manifest.yaml")?;
{
let mut tarball = Builder::new(&mut encoder);

tarball
.append_file(lib_name, &mut lib_file)
.context(format!(
"failed to append lib{normalized_package_name}.{LIB_EXT}"
))?;

tarball
.append_file("manifest.yaml", &mut manifest_file)
.context("failed to add manifest.yaml to archive")?;

tarball
.append_dir_all("migrations", build_dir.join("migrations"))
.context("failed to append \"migrations\" to archive")?;
archive_if_exists(&plugin_build_dir.join(&lib_name), &mut tarball)?;
archive_if_exists(&plugin_build_dir.join("manifest.yaml"), &mut tarball)?;
archive_if_exists(&plugin_build_dir.join("migrations"), &mut tarball)?;
archive_if_exists(&plugin_build_dir.join("assets"), &mut tarball)?;
}

encoder.finish()?;

Ok(())
}

fn archive_if_exists(file_path: &Path, tarball: &mut Builder<&mut GzEncoder<File>>) -> Result<()> {
if file_path.exists() {
if file_path.is_dir() {
tarball
.append_dir_all(file_path.file_name().unwrap(), file_path)
.context(format!(
"failed to append directory: {} to archive",
file_path.display()
))?;
} else {
let mut opened_file = File::open(file_path)
.context(format!("failed to open file {}", &file_path.display()))?;

tarball
.append_file(file_path.file_name().unwrap(), &mut opened_file)
.context(format!(
"failed to append file: {} to archive",
file_path.display()
))?;
}
} else {
log::info!(
"Couldn't find {} while packing plugin - skipping.",
file_path.display()
);
}

Ok(())
}

fn get_latest_plugin_version(plugin_dir: &Path) -> Result<String> {
let cargo_toml =
fs::read_to_string(plugin_dir.join("Cargo.toml")).expect("Failed to read Cargo.toml");

let parsed: toml::Value = toml::de::from_str(&cargo_toml).expect("Failed to parse TOML");

if let Some(package) = parsed.get("package") {
if let Some(version) = package.get("version") {
return Ok(version
.to_string()
.strip_prefix("\"")
.unwrap()
.strip_suffix("\"")
.unwrap()
.to_string());
}
bail!("Couldn't find version in plugin Cargo.toml");
}

bail!(
"Couldn't resolve plugin version from Cargo.toml at {}",
plugin_dir.display()
)
}
125 changes: 90 additions & 35 deletions src/helpers/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,35 @@ fn get_output_path() -> PathBuf {
}

#[derive(Debug, Builder)]
pub struct Params {}
pub struct Params {
#[builder(default)]
#[builder(setter(custom))]
custom_assets: Vec<PathBuf>,
}

impl ParamsBuilder {
pub fn custom_assets<I, S>(&mut self, assets: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let custom_assets: Vec<std::path::PathBuf> = assets
.into_iter()
.map(|asset| asset.as_ref().into())
.collect();

self.custom_assets = Some(custom_assets);

pub fn main(_params: &Params) {
self
}
}

pub fn main(params: &Params) {
let out_dir = get_output_path();
let out_manifest_path = Path::new(&out_dir).join("manifest.yaml");
let pkg_version = env::var("CARGO_PKG_VERSION").unwrap();
let pkg_name = env::var("CARGO_PKG_NAME").unwrap();
let plugin_path = out_dir.join(&pkg_name).join(&pkg_version);
let out_manifest_path = plugin_path.join("manifest.yaml");
let lib_name = format!("lib{}.{LIB_EXT}", pkg_name.replace('-', "_"));

dir::remove(&plugin_path).unwrap();
Expand Down Expand Up @@ -60,53 +81,87 @@ pub fn main(_params: &Params) {
}
}

// Generate folder with custom assets
fs::create_dir(plugin_path.join("assets")).unwrap();

// Generate new manifest.yaml and migrations from template
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let crate_dir = Path::new(&crate_dir);

let template_path = crate_dir.join(MANIFEST_TEMPLATE_NAME);
let template =
fs::read_to_string(template_path).expect("template for manifest plugin not found");
let template = liquid::ParserBuilder::with_stdlib()
.build()
.unwrap()
.parse(&template)
.expect("invalid manifest template");

let migrations_dir = crate_dir.join("migrations");
let migrations: Vec<String> = match fs::read_dir(&migrations_dir) {
Ok(dir) => dir
.map(|path| {
path.unwrap()
.path()
.strip_prefix(crate_dir)
.unwrap()
.to_string_lossy()
.into()
})
.collect(),
Err(_) => Vec::new(),
};

let template_ctx = liquid::object!({
"version": pkg_version,
"migrations": migrations,
});

fs::write(&out_manifest_path, template.render(&template_ctx).unwrap()).unwrap();
let mut migrations: Vec<String> = vec![];
if migrations_dir.exists() {
migrations = match fs::read_dir(&migrations_dir) {
Ok(dir) => dir
.map(|path| {
path.unwrap()
.path()
.strip_prefix(crate_dir)
.unwrap()
.to_string_lossy()
.into()
})
.collect(),
Err(_) => Vec::new(),
};
}

// Copy migrations directory and manifest into newest plugin version
if !migrations.is_empty() {
let mut cp_opts = CopyOptions::new();
cp_opts.overwrite = true;
dir::copy(&migrations_dir, &out_dir, &cp_opts).unwrap();
dir::copy(out_dir.join("migrations"), &plugin_path, &cp_opts).unwrap();
dir::copy(&migrations_dir, &plugin_path, &cp_opts).unwrap();
}

if crate_dir.join(MANIFEST_TEMPLATE_NAME).exists() {
let template_path = crate_dir.join(MANIFEST_TEMPLATE_NAME);
let template =
fs::read_to_string(template_path).expect("template for manifest plugin not found");
let template = liquid::ParserBuilder::with_stdlib()
.build()
.unwrap()
.parse(&template)
.expect("invalid manifest template");

let template_ctx = liquid::object!({
"version": pkg_version,
"migrations": migrations,
});

fs::write(&out_manifest_path, template.render(&template_ctx).unwrap()).unwrap();
} else {
log::warn!(
"Couldn't find manifest.yaml template at {}, skipping its generation...",
crate_dir.display()
);
}
fs::copy(out_manifest_path, plugin_path.join("manifest.yaml")).unwrap();

// Create symlinks for newest plugin version, which would be created after build.rs script
std::os::unix::fs::symlink(out_dir.join(&lib_name), plugin_path.join(lib_name)).unwrap();

// Move custom assets into plugin folder
for asset_path in &params.custom_assets {
if !asset_path.exists() {
println!(
"cargo::warning=Couldn't find custom asset {} - skipping",
asset_path.display(),
);

continue;
}
if asset_path.is_dir() {
dir::copy(asset_path, plugin_path.join("assets"), &CopyOptions::new()).unwrap();
} else {
fs::copy(
asset_path,
plugin_path
.join("assets")
.join(asset_path.file_name().unwrap()),
)
.unwrap();
}
}

// Trigger on Cargo.toml change in order not to run cargo update each time
// version is changed
println!("cargo::rerun-if-changed=Cargo.toml");
Expand Down

0 comments on commit 98f7ac8

Please sign in to comment.