Skip to content
This repository has been archived by the owner on Jul 17, 2024. It is now read-only.

Commit

Permalink
new: Initial spike.
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed Aug 10, 2023
1 parent 0d6448d commit 0e6851a
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: moonrepo
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
push:
branches:
- master
pull_request:

jobs:
format:
name: Format
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
steps:
- uses: actions/checkout@v3
- uses: moonrepo/setup-rust@v1
with:
components: rustfmt
- run: cargo fmt --all --check
lint:
name: Lint
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
steps:
- uses: actions/checkout@v3
- uses: moonrepo/setup-rust@v1
with:
components: clippy
- run: cargo clippy --workspace --all-targets
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
steps:
- uses: actions/checkout@v3
- uses: moonrepo/setup-rust@v1
with:
bins: cargo-wasi, cargo-nextest
- run: cargo wasi build
- run: cargo nextest run
2 changes: 2 additions & 0 deletions .prototools
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[plugins]
schema-test = "source:target/wasm32-wasi/debug/schema_plugin.wasm"
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

## 0.0.1

#### 🎉 Release

- Initial release!
25 changes: 25 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "schema_plugin"
version = "0.0.1"
edition = "2021"
license = "MIT"
publish = false

[lib]
crate-type = ['cdylib']

[dependencies]
extism-pdk = "0.3.3"
proto_pdk = "0.4.2"
proto_schema_plugin = { version = "0.8.1", path = "../../proto/crates/schema-plugin" }
regex = "1.9.3"
serde = "1.0.183"
serde_json = "1.0.104"
starbase_utils = { version = "0.2.17", default-features = false, features = [
"toml",
] }

[dev-dependencies]
proto_pdk_test_utils = "0.3.3"
starbase_sandbox = "0.1.8"
tokio = "1.30.0"
18 changes: 18 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
MIT License

Copyright (c) 2023 moonrepo, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Schema plugin

WASM plugin for [proto](https://github.com/moonrepo/proto) that is powered by a schema. Currently supports TOML.

## Contributing

Build the plugin:

```shell
cargo build --target wasm32-wasi
```

Test the plugin by running `proto` commands. Requires proto >= v0.12.

```shell
proto install schema-test
proto list-remote schema-test
```
3 changes: 3 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
profile = "default"
channel = "1.71.1"
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// WASM cannot be executed through the test runner and we need to avoid building
// WASM code for non-WASM targets. We can solve both of these with a cfg flag.

mod proto;

pub use proto::*;
227 changes: 227 additions & 0 deletions src/proto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
use extism_pdk::*;
use proto_pdk::*;
use proto_schema_plugin::{PlatformMapper, Schema, SchemaType};
use serde_json::Value as JsonValue;
use starbase_utils::toml;
use std::path::PathBuf;

#[host_fn]
extern "ExtismHost" {
fn exec_command(input: Json<ExecCommandInput>) -> Json<ExecCommandOutput>;
}

fn get_schema() -> Result<Schema, Error> {
let data = config::get("schema").expect("Missing schema!");
let schema: Schema = toml::from_str(&data)?;

Ok(schema)
}

fn get_platform<'schema>(
schema: &'schema Schema,
env: &Environment,
) -> Result<&'schema PlatformMapper, PluginError> {
let os = env.os.to_string();
let mut platform = schema.platform.get(&os);

// Fallback to linux for other OSes
if platform.is_none()
&& (env.os == HostOS::FreeBSD || env.os == HostOS::NetBSD || env.os == HostOS::OpenBSD)
{
platform = schema.platform.get("linux");
}

platform.ok_or_else(|| PluginError::UnsupportedOS {
tool: schema.name.clone(),
os,
})
}

fn get_bin_path(platform: &PlatformMapper, env: &Environment) -> PathBuf {
platform
.bin_path
.clone()
.unwrap_or_else(|| format_bin_name(&env.id, env.os))
.into()
}

#[plugin_fn]
pub fn register_tool(Json(_): Json<ToolMetadataInput>) -> FnResult<Json<ToolMetadataOutput>> {
let schema = get_schema()?;

Ok(Json(ToolMetadataOutput {
name: schema.name,
type_of: match schema.type_of {
SchemaType::Cli => PluginType::CLI,
SchemaType::DependencyManager => PluginType::DependencyManager,
SchemaType::Language => PluginType::Language,
},
..ToolMetadataOutput::default()
}))
}

fn is_musl() -> bool {
if cfg!(macos) {
false
} else {
unsafe {
match exec_command(Json(ExecCommandInput::pipe("ldd", ["--version"]))) {
Ok(res) => res.0.stdout.contains("musl"),
Err(_) => false,
}
}
}
}

fn interpolate_tokens(value: &str, schema: &Schema, env: &Environment) -> String {
let arch = env.arch.to_rust_arch();
let os = env.os.to_string();

let mut value = value
.replace("{version}", &env.version)
.replace("{arch}", schema.install.arch.get(&arch).unwrap_or(&arch))
.replace("{os}", &os);

// Avoid detecting musl unless requested
if value.contains("{libc}") {
value = value.replace("{libc}", if is_musl() { "musl" } else { "gnu" });
}

value
}

#[plugin_fn]
pub fn download_prebuilt(
Json(input): Json<DownloadPrebuiltInput>,
) -> FnResult<Json<DownloadPrebuiltOutput>> {
let schema = get_schema()?;
let platform = get_platform(&schema, &input.env)?;

let download_file = interpolate_tokens(&platform.download_file, &schema, &input.env);
let download_url = interpolate_tokens(&schema.install.download_url, &schema, &input.env)
.replace("{download_file}", &download_file);

let checksum_file = interpolate_tokens(
platform
.checksum_file
.as_ref()
.unwrap_or(&"CHECKSUM.txt".to_string()),
&schema,
&input.env,
);
let checksum_url = schema.install.checksum_url.as_ref().map(|url| {
interpolate_tokens(&url, &schema, &input.env).replace("{checksum_file}", &checksum_file)
});

Ok(Json(DownloadPrebuiltOutput {
archive_prefix: platform.archive_prefix.clone(),
checksum_url,
checksum_name: Some(checksum_file),
download_url,
download_name: Some(download_file),
}))
}

#[plugin_fn]
pub fn locate_bins(Json(input): Json<LocateBinsInput>) -> FnResult<Json<LocateBinsOutput>> {
let schema = get_schema()?;
let platform = get_platform(&schema, &input.env)?;

Ok(Json(LocateBinsOutput {
bin_path: Some(get_bin_path(platform, &input.env)),
fallback_last_globals_dir: true,
globals_lookup_dirs: schema.globals.lookup_dirs,
globals_prefix: schema.globals.package_prefix,
..LocateBinsOutput::default()
}))
}

pub fn remove_v_prefix(value: &str) -> &str {
if value.starts_with('v') || value.starts_with('V') {
return &value[1..];
}

value
}

#[plugin_fn]
pub fn load_versions(Json(_): Json<LoadVersionsInput>) -> FnResult<Json<LoadVersionsOutput>> {
let schema = get_schema()?;

if let Some(repository) = schema.resolve.git_url {
let pattern = regex::Regex::new(&schema.resolve.git_tag_pattern)?;

let tags = load_git_tags(&repository)?
.into_iter()
.filter_map(|t| {
pattern
.captures(&t)
.map(|captures| remove_v_prefix(captures.get(1).unwrap().as_str()).to_string())
})
.collect::<Vec<_>>();

return Ok(Json(LoadVersionsOutput::from_tags(&tags)?));
}

if let Some(endpoint) = schema.resolve.manifest_url {
let response: Vec<JsonValue> = fetch_url_with_cache(endpoint)?;
let version_key = &schema.resolve.manifest_version_key;
let mut versions = vec![];

for row in response {
match row {
JsonValue::String(v) => {
versions.push(remove_v_prefix(&v).to_string());
}
JsonValue::Object(o) => {
if let Some(JsonValue::String(v)) = o.get(version_key) {
versions.push(remove_v_prefix(&v).to_string());
}
}
_ => {}
}
}

return Ok(Json(LoadVersionsOutput::from_tags(&versions)?));
}

err!(
"Unable to resolve versions for {}. Schema either requires a `git_url` or `manifest_url`."
.into()
)
}

#[plugin_fn]
pub fn detect_version_files(_: ()) -> FnResult<Json<DetectVersionOutput>> {
let mut output = DetectVersionOutput::default();
let schema = get_schema()?;

if let Some(files) = schema.detect.version_files {
output.files = files;
}

Ok(Json(output))
}

#[plugin_fn]
pub fn create_shims(Json(input): Json<CreateShimsInput>) -> FnResult<Json<CreateShimsOutput>> {
let mut output = CreateShimsOutput::default();
let schema = get_schema()?;
let platform = get_platform(&schema, &input.env)?;
let bin_path = get_bin_path(platform, &input.env);

output.no_primary_global = !schema.shim.global;

if schema.shim.local {
output.local_shims.insert(
input.env.id,
if let Some(parent_bin) = schema.shim.parent_bin {
ShimConfig::local_with_parent(bin_path, parent_bin)
} else {
ShimConfig::local(bin_path)
},
);
}

Ok(Json(output))
}

0 comments on commit 0e6851a

Please sign in to comment.