Skip to content

Commit 91d6326

Browse files
authored
Merge pull request #17 from topheman/feat/add-homebrew-and-cross-compile
Add Homebrew Support and Cross-Compilation Pipeline
2 parents b0c06c3 + 06a7e9c commit 91d6326

File tree

9 files changed

+276
-14
lines changed

9 files changed

+276
-14
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# inspired by https://github.com/topheman/update-homebrew-tap-playground/blob/master/.github/workflows/cross-compile.yml
2+
name: Cross-Compile
3+
4+
on: [push]
5+
6+
env:
7+
CARGO_TERM_COLOR: always
8+
BINARY_NAME: pluginlab
9+
10+
jobs:
11+
build:
12+
if: github.ref_type == 'tag'
13+
name: Cross-Compile ${{ matrix.platform.target }}
14+
strategy:
15+
matrix:
16+
platform:
17+
18+
- runs-on: ubuntu-24.04
19+
target: x86_64-unknown-linux-gnu
20+
- runs-on: ubuntu-24.04-arm
21+
target: aarch64-unknown-linux-gnu
22+
- runs-on: macos-14
23+
target: x86_64-apple-darwin
24+
- runs-on: macos-14
25+
target: aarch64-apple-darwin
26+
runs-on: ${{ matrix.platform.runs-on }}
27+
steps:
28+
- name: Checkout source code
29+
uses: actions/checkout@v4
30+
- name: Build binary
31+
uses: houseabsolute/actions-rust-cross@v1
32+
with:
33+
command: build
34+
target: ${{ matrix.platform.target }}
35+
args: "--locked --release -p pluginlab"
36+
strip: true
37+
- name: Generate completions
38+
run: |
39+
mkdir -p ./tmp/${{ matrix.platform.target }}
40+
cp target/${{ matrix.platform.target }}/release/${{ env.BINARY_NAME }} ./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }}
41+
mkdir -p ./tmp/${{ matrix.platform.target }}/completions/{zsh,bash,fish}
42+
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell zsh > ./tmp/${{ matrix.platform.target }}/completions/zsh/_${{ env.BINARY_NAME }}
43+
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell bash > ./tmp/${{ matrix.platform.target }}/completions/bash/${{ env.BINARY_NAME }}
44+
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell fish > ./tmp/${{ matrix.platform.target }}/completions/fish/${{ env.BINARY_NAME }}.fish
45+
- name: Compress
46+
run: |
47+
mkdir -p ./compressed
48+
cd ./tmp/${{ matrix.platform.target }}
49+
tar -cvf ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz ${{ env.BINARY_NAME }} completions
50+
mv ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz ../../compressed
51+
- name: Cache compressed binaries
52+
uses: actions/cache@v4
53+
with:
54+
path: ./compressed
55+
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
56+
upload-archives:
57+
if: false # This step is totally skipped, activate the workflow outside of a tag (for debug purposes)
58+
needs: build
59+
name: Upload archive for build ${{ matrix.platform.target }}
60+
strategy:
61+
matrix:
62+
platform:
63+
- target: x86_64-unknown-linux-gnu
64+
- target: aarch64-unknown-linux-gnu
65+
- target: x86_64-apple-darwin
66+
- target: aarch64-apple-darwin
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Restore cached compressed binaries
70+
uses: actions/cache/restore@v4
71+
with:
72+
path: ./compressed
73+
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
74+
- name: Upload archive
75+
uses: actions/upload-artifact@v4
76+
with:
77+
path: ./compressed/
78+
name: ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz
79+
retention-days: 10
80+
create-release-draft-if-not-exist:
81+
if: github.ref_type == 'tag'
82+
permissions:
83+
contents: write
84+
needs: build
85+
runs-on: ubuntu-latest
86+
steps:
87+
- uses: actions/checkout@v4
88+
- name: Create release draft
89+
uses: topheman/create-release-if-not-exist@v1
90+
with:
91+
args: ${{ github.ref_name }} --draft --generate-notes
92+
upload-artifacts-to-release-draft:
93+
if: github.ref_type == 'tag'
94+
permissions:
95+
contents: write
96+
needs: create-release-draft-if-not-exist
97+
env:
98+
RELEASE_NAME: ${{ github.ref_name }}
99+
name: Upload artifacts to release draft ${{ matrix.platform.target }}
100+
strategy:
101+
matrix:
102+
platform:
103+
- target: x86_64-unknown-linux-gnu
104+
- target: aarch64-unknown-linux-gnu
105+
- target: x86_64-apple-darwin
106+
- target: aarch64-apple-darwin
107+
runs-on: ubuntu-latest
108+
steps:
109+
- uses: actions/checkout@v4
110+
- name: Restore cached compressed binaries
111+
uses: actions/cache/restore@v4
112+
with:
113+
path: ./compressed
114+
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
115+
- name: Upload artifacts to release draft
116+
run: |
117+
gh release upload ${{ env.RELEASE_NAME }} ./compressed/${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz
118+
env:
119+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/rust-host.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,22 @@ jobs:
8080
contents: write
8181
runs-on: ubuntu-latest
8282
needs: build-and-test
83+
env:
84+
RELEASE_NAME: ${{ github.ref_name }}
8385
steps:
86+
- uses: actions/checkout@v4
8487
- name: Restore cached wasm files
8588
id: cache-wasm-files-restore
8689
uses: actions/cache/restore@v4
8790
with:
8891
path: ./tmp/plugins
8992
key: ${{ runner.os }}-wasm-files-${{ github.sha }}
90-
- name: Release draft
91-
uses: softprops/action-gh-release@v2
93+
- name: Create release draft if it doesn't exist
94+
uses: topheman/create-release-if-not-exist@v1
9295
with:
93-
draft: true
94-
files: ./tmp/plugins/*.wasm
96+
args: ${{ env.RELEASE_NAME }} --draft --generate-notes
97+
- name: Upload wasm files to release draft
98+
run: |
99+
gh release upload ${{ env.RELEASE_NAME }} ./tmp/plugins/*.wasm
100+
env:
101+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Update Homebrew Tap
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
update-homebrew-tap:
9+
runs-on: ubuntu-latest
10+
11+
permissions:
12+
contents: write # ✅ minimal required for pushing commits
13+
14+
steps:
15+
- name: Checkout source repo
16+
uses: actions/checkout@v4
17+
- name: Update Homebrew Formula
18+
uses: topheman/update-homebrew-tap@v2
19+
with:
20+
formula-target-repository: topheman/homebrew-tap
21+
formula-target-file: Formula/pluginlab.rb
22+
tar-files: |
23+
{
24+
"linuxArm": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-aarch64-unknown-linux-gnu.tar.gz",
25+
"linuxIntel": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-x86_64-unknown-linux-gnu.tar.gz",
26+
"macArm": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-aarch64-apple-darwin.tar.gz",
27+
"macIntel": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-x86_64-apple-darwin.tar.gz"
28+
}
29+
metadata: |
30+
{
31+
"version": "${{ github.ref_name }}",
32+
"binaryName": "pluginlab",
33+
"description": "Terminal REPL with sandboxed multi-language plugin system - unified codebase runs in CLI (Rust) and web (TypeScript)",
34+
"homepage": "https://github.com/topheman/webassembly-component-model-experiments",
35+
"license": "MIT"
36+
}
37+
github-token: ${{ secrets.HOMEBREW_TAP_TOKEN }}

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,20 @@ In the last seven years I've done a few projects involving rust and WebAssembly:
6565

6666
### pluginlab (rust) - REPL cli host
6767

68-
#### Install
68+
#### Install the binary
69+
70+
**Using cargo (from source)**
6971

7072
```bash
71-
# Install the pluginlab binary
7273
cargo install pluginlab
7374
```
7475

76+
**Using homebrew**
77+
78+
```bash
79+
brew install topheman/tap/pluginlab
80+
```
81+
7582
#### Run
7683

7784
```bash

crates/pluginlab/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ clap = { workspace = true }
2424
tokio = { workspace = true }
2525
anyhow = { workspace = true }
2626
reqwest = "0.12.20"
27+
clap_complete = "4.5"
2728

2829
[build-dependencies]
2930
wasmtime = { workspace = true }

crates/pluginlab/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@ More details on the github repo: [topheman/webassembly-component-model-experimen
4545

4646
## Install
4747

48+
**Using cargo (from source)**
49+
4850
```bash
4951
cargo install pluginlab
5052
```
5153

54+
**Using homebrew**
55+
56+
```bash
57+
brew install topheman/tap/pluginlab
58+
```
59+
5260
## Usage
5361

5462
Run the CLI host, loading the latest versions of the plugins from the web (you can also load them from local files).

crates/pluginlab/src/cli.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
use clap::Parser;
1+
use clap::{Parser, Subcommand, ValueEnum};
22
use std::path::PathBuf;
33

44
#[derive(Parser, Debug)]
55
#[command(author, version, about, long_about = None)]
66
pub struct Cli {
7+
#[command(subcommand)]
8+
pub command: Option<Commands>,
9+
710
/// Paths or URLs to WebAssembly plugin files
811
#[arg(long)]
912
pub plugins: Vec<String>,
1013

1114
/// Path or URL to WebAssembly REPL logic file
1215
#[arg(long)]
13-
pub repl_logic: String,
16+
pub repl_logic: Option<String>,
1417

1518
#[arg(long, default_value_t = false)]
1619
pub debug: bool,
@@ -46,3 +49,20 @@ pub struct Cli {
4649
)]
4750
pub allow_all: bool,
4851
}
52+
53+
#[derive(Subcommand, Debug)]
54+
pub enum Commands {
55+
/// Generate completions for your own shell (shipped with the homebrew version)
56+
GenerateCompletions {
57+
/// Specify which shell you target - accepted values: bash, fish, zsh
58+
#[arg(long, value_enum)]
59+
shell: AvailableShells,
60+
},
61+
}
62+
63+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
64+
pub enum AvailableShells {
65+
Bash,
66+
Fish,
67+
Zsh,
68+
}

crates/pluginlab/src/lib.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,68 @@ pub(crate) use wasm_host::WasmHost;
1212
use anyhow::Result;
1313
use api::host_api::repl::api::transport;
1414
use clap::Parser;
15-
use cli::Cli;
15+
use cli::{Cli, Commands};
1616
use helpers::{StatusHandler, StdoutHandler};
1717
use std::io::Write;
1818

1919
/// Main entry point for the REPL application
2020
pub async fn run_async() -> Result<()> {
2121
// Parse command line arguments
2222
let cli = Cli::parse();
23+
24+
// Handle subcommands first
25+
if let Some(command) = &cli.command {
26+
match command {
27+
Commands::GenerateCompletions { shell } => {
28+
return handle_generate_completions(*shell);
29+
}
30+
}
31+
}
32+
33+
// For REPL mode, repl_logic is required
34+
let repl_logic = cli
35+
.repl_logic
36+
.ok_or_else(|| anyhow::anyhow!("--repl-logic is required when running in REPL mode"))?;
37+
2338
let debug = cli.debug;
39+
let plugins = cli.plugins;
40+
let dir = cli.dir;
41+
let allow_net = cli.allow_net;
42+
let allow_read = cli.allow_read;
43+
let allow_write = cli.allow_write;
44+
let allow_all = cli.allow_all;
45+
46+
// Create a new CLI struct for the remaining operations
47+
let repl_cli = Cli {
48+
command: None,
49+
plugins,
50+
repl_logic: Some(repl_logic.clone()),
51+
debug,
52+
dir,
53+
allow_net,
54+
allow_read,
55+
allow_write,
56+
allow_all,
57+
};
58+
2459
println!("[Host] Starting REPL host...");
2560

2661
// Create a WASI context for the host
2762
// Binding stdio, args, env, preopened dir ...
28-
let wasi_ctx = WasmEngine::build_wasi_ctx(&cli)?;
63+
let wasi_ctx = WasmEngine::build_wasi_ctx(&repl_cli)?;
2964

3065
// Create the WebAssembly engine
3166
let engine = WasmEngine::new()?;
3267

3368
// Create the host
34-
let mut host = WasmHost::new(&engine, wasi_ctx, &cli);
69+
let mut host = WasmHost::new(&engine, wasi_ctx, &repl_cli);
3570

36-
println!("[Host] Loading REPL logic from: {}", cli.repl_logic);
71+
println!("[Host] Loading REPL logic from: {}", repl_logic);
3772
// Override the REPL logic in the binary with the one passed by params
38-
host.load_repl_logic(&engine, &cli.repl_logic).await?;
73+
host.load_repl_logic(&engine, &repl_logic).await?;
3974

4075
// Load plugins
41-
for plugin_source in &cli.plugins {
76+
for plugin_source in &repl_cli.plugins {
4277
println!("[Host] Loading plugin: {}", plugin_source);
4378
host.load_plugin(&engine, plugin_source).await?;
4479
}
@@ -216,3 +251,21 @@ pub async fn run_async() -> Result<()> {
216251
}
217252
}
218253
}
254+
255+
/// Handle the generate-completions subcommand
256+
fn handle_generate_completions(shell: cli::AvailableShells) -> Result<()> {
257+
use clap::CommandFactory;
258+
use clap_complete::{generate, Shell};
259+
use cli::Cli;
260+
261+
let mut cmd = Cli::command();
262+
let shell_type = match shell {
263+
cli::AvailableShells::Bash => Shell::Bash,
264+
cli::AvailableShells::Fish => Shell::Fish,
265+
cli::AvailableShells::Zsh => Shell::Zsh,
266+
};
267+
268+
generate(shell_type, &mut cmd, "pluginlab", &mut std::io::stdout());
269+
270+
Ok(())
271+
}

0 commit comments

Comments
 (0)