-
-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support asdf plugins through a proto WASM plugin #540
base: master
Are you sure you want to change the base?
Changes from 11 commits
1cc4574
4acf116
7706548
eb4c84b
19a70b1
c4b84a0
d4e6dcc
e2836df
2cd736c
7c1923e
1c4ac9f
27bfd7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
name = "asdf-plugin" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[lib] | ||
crate-type = ['cdylib'] | ||
|
||
[dependencies] | ||
proto_pdk = { path = "../../crates/pdk" } | ||
proto_core = { version = "0.38.2", path = "../core" } | ||
extism-pdk = { workspace = true } | ||
serde = { workspace = true } | ||
serde_json = { workspace = true } | ||
dirs = "5.0.1" | ||
|
||
[dev-dependencies] | ||
proto_pdk_test_utils = { path = "../../crates/pdk-test-utils" } | ||
starbase_sandbox = "*" | ||
tokio = { workspace = true } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
use extism_pdk::*; | ||
use proto_core::Tool; | ||
use proto_pdk::*; | ||
use serde::Deserialize; | ||
use std::env; | ||
use std::path::Path; | ||
use dirs; | ||
|
||
#[derive(Debug, Default, Deserialize)] | ||
#[serde(default, deny_unknown_fields, rename_all = "kebab-case")] | ||
pub struct AsdfConfig { | ||
pub asdf_plugin: Option<String>, | ||
pub asdf_repository: Option<String>, | ||
} | ||
|
||
pub struct AsdfPlugin { | ||
pub tool: Tool, | ||
} | ||
|
||
impl AsdfPlugin { | ||
pub fn detect_version_files(&self) -> DetectVersionOutput { | ||
DetectVersionOutput { | ||
files: vec![".tool-versions".into()], | ||
ignore: vec![], | ||
} | ||
} | ||
|
||
pub fn parse_version_file(&self, input: ParseVersionFileInput) -> ParseVersionFileOutput { | ||
let mut version = None; | ||
if input.file == ".tool-versions" { | ||
for line in input.content.lines() { | ||
if let Some((tool, version_str)) = line.split_once(' ') { | ||
if tool == self.tool.get_name() { | ||
version = Some(UnresolvedVersionSpec::parse(version_str.trim()).unwrap()); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
ParseVersionFileOutput { version } | ||
} | ||
|
||
pub fn download_prebuilt(&self, input: DownloadPrebuiltInput) -> DownloadPrebuiltOutput { | ||
let env = get_host_environment().unwrap(); | ||
|
||
check_supported_os_and_arch( | ||
"ASDF Plugin", | ||
&env, | ||
permutations![ | ||
HostOS::Linux => [HostArch::X64, HostArch::Arm64, HostArch::Arm, HostArch::Powerpc64, HostArch::S390x], | ||
HostOS::MacOS => [HostArch::X64, HostArch::Arm64], | ||
HostOS::Windows => [HostArch::X64, HostArch::X86, HostArch::Arm64], | ||
], | ||
).unwrap(); | ||
|
||
let version = input.context.version; | ||
let arch = env.arch; | ||
let os = env.os; | ||
|
||
let prefix = match os { | ||
HostOS::Linux => format!("asdf-plugin-v{version}-linux-{arch}"), | ||
HostOS::MacOS => format!("asdf-plugin-v{version}-darwin-{arch}"), | ||
HostOS::Windows => format!("asdf-plugin-v{version}-win-{arch}"), | ||
other => { | ||
return DownloadPrebuiltOutput { | ||
download_url: format!("Unsupported platform: {}", other), | ||
..DownloadPrebuiltOutput::default() | ||
}; | ||
} | ||
}; | ||
|
||
let filename = if os == HostOS::Windows { | ||
format!("{prefix}.zip") | ||
} else { | ||
format!("{prefix}.tar.xz") | ||
}; | ||
|
||
let config: AsdfConfig = self.tool.config(); | ||
let asdf_plugin = config | ||
.asdf_plugin | ||
.unwrap_or_else(|| self.tool.get_name().to_string()); | ||
let repository = config | ||
.asdf_repository | ||
.unwrap_or_else(|| format!("https://github.com/asdf-vm/asdf-{}.git", asdf_plugin)); | ||
|
||
DownloadPrebuiltOutput { | ||
archive_prefix: Some(prefix), | ||
download_url: format!("{repository}/releases/download/v{version}/{filename}"), | ||
download_name: Some(filename), | ||
checksum_url: Some(format!( | ||
"{repository}/releases/download/v{version}/SHA256SUMS" | ||
)), | ||
checksum_public_key: Some("public-key-string".into()), // Need to adjust if applicable | ||
..DownloadPrebuiltOutput::default() | ||
} | ||
} | ||
|
||
pub fn install_plugin(&self, repository: &str) { | ||
let asdf_dir = match env::var("ASDF_DATA_DIR") { | ||
Ok(val) => Path::new(&val).to_path_buf(), | ||
Err(_) => dirs::home_dir().unwrap().join(".asdf"), | ||
}; | ||
|
||
let plugin_dir = asdf_dir.join("plugins").join(self.tool.get_name()); | ||
|
||
if !plugin_dir.exists() { | ||
std::fs::create_dir_all(&plugin_dir).unwrap(); | ||
} | ||
|
||
std::process::Command::new("git") | ||
.arg("clone") | ||
.arg(repository) | ||
.arg(&plugin_dir) | ||
.output() | ||
.expect("Failed to clone asdf plugin"); | ||
} | ||
|
||
pub fn pre_install(&self, mut input: InstallHook) { | ||
let config: AsdfConfig = self.tool.config(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had confusion here about referencing config. Any guidance on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
let repository = config | ||
.asdf_repository | ||
.unwrap_or_else(|| format!("https://github.com/asdf-vm/asdf-{}.git", self.tool.get_name())); | ||
self.install_plugin(&repository); | ||
input.context = self.prepare_context(input.context); | ||
self.tool | ||
.plugin | ||
.call_func_without_output("pre_install", input) | ||
.unwrap(); | ||
} | ||
|
||
fn prepare_context(&self, context: ToolContext) -> ToolContext { | ||
let dir = if context.tool_dir.any_path().components().count() == 0 { | ||
self.tool.get_product_dir() | ||
} else { | ||
context.tool_dir.any_path().to_path_buf() | ||
}; | ||
ToolContext { | ||
tool_dir: self.tool.to_virtual_path(&dir), | ||
..context | ||
} | ||
} | ||
} | ||
|
||
// register_tool: Registers the plugin and provides metadata. | ||
#[plugin_fn] | ||
pub fn register_tool(Json(input): Json<ToolMetadataInput>) -> FnResult<Json<ToolMetadataOutput>> { | ||
Ok(Json(ToolMetadataOutput { | ||
name: "ASDF Plugin".into(), | ||
type_of: PluginType::Language, | ||
plugin_version: Some(env!("CARGO_PKG_VERSION").into()), | ||
..ToolMetadataOutput::default() | ||
})) | ||
} | ||
|
||
// download_prebuilt: Handles downloading the pre-built tool, with URL construction based on OS and architecture. | ||
#[plugin_fn] | ||
pub fn download_prebuilt( | ||
Json(input): Json<DownloadPrebuiltInput>, | ||
) -> FnResult<Json<DownloadPrebuiltOutput>> { | ||
let asdf_plugin = AsdfPlugin { | ||
tool: Tool::default(), | ||
}; | ||
Ok(Json(asdf_plugin.download_prebuilt(input))) | ||
} | ||
|
||
// unpack_archive: Unpacks downloaded archives based on their file extension. | ||
#[plugin_fn] | ||
pub fn unpack_archive(Json(input): Json<UnpackArchiveInput>) -> FnResult<()> { | ||
let input_file = input.input_file; | ||
let output_dir = input.output_dir; | ||
|
||
// Need to ensure file type and unpack accordingly | ||
if input_file.ends_with(".tar.xz") { | ||
//TODO: Implement the untar and unzip moments | ||
// untar(input_file, output_dir)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this untar and unzip could be custom implemented or any suggested crates for it? |
||
} else if input_file.ends_with(".zip") { | ||
// unzip(input_file, output_dir)?; | ||
} else { | ||
return Err( | ||
PluginError::UnsupportedArchiveFormat(format!("Unsupported archive format: {}", input_file)).into(), | ||
); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
// detect_version_files: Specifies which files to check for version information. | ||
#[plugin_fn] | ||
pub fn detect_version_files(_: ()) -> FnResult<Json<DetectVersionOutput>> { | ||
let asdf_plugin = AsdfPlugin { | ||
tool: Tool::default(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no default method for initializing an instance of AsdfPlugin with a default instance of Tool. Can it be implemented in Tool impl? |
||
}; | ||
Ok(Json(asdf_plugin.detect_version_files())) | ||
} | ||
|
||
// parse_version_file: Parses version information from specified files | ||
#[plugin_fn] | ||
pub fn parse_version_file( | ||
Json(input): Json<ParseVersionFileInput>, | ||
) -> FnResult<Json<ParseVersionFileOutput>> { | ||
let asdf_plugin = AsdfPlugin { | ||
tool: Tool::default(), | ||
}; | ||
Ok(Json(asdf_plugin.parse_version_file(input))) | ||
} | ||
|
||
// locate_executables: Locates the installed tool's executable files. | ||
#[plugin_fn] | ||
pub fn locate_executables( | ||
Json(_): Json<LocateExecutablesInput>, | ||
) -> FnResult<Json<LocateExecutablesOutput>> { | ||
let env = get_host_environment()?; | ||
|
||
Ok(Json(LocateExecutablesOutput { | ||
primary: Some(ExecutableConfig::new( | ||
env.os.for_native("bin/node", "node.exe"), | ||
)), | ||
globals_lookup_dirs: vec!["$DENO_INSTALL_ROOT/bin".into(), "$HOME/.deno/bin".into()], | ||
..LocateExecutablesOutput::default() | ||
})) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plugins shouldn't use
proto_core
at all. Refer to the other plugins as a reference: https://github.com/moonrepo/node-plugin