Skip to content
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

docs: Update readme, and polish. #2

Merged
merged 2 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,80 @@
# Build proto plugin

This action will build Rust-based [proto WASM plugins](https://moonrepo.dev/docs/proto/wasm-plugin)
for distribution. It achieves this by:

- Finding all buildable packages using `cargo metadata`.
- Builds all packages using `cargo build --release --target wasm32-wasi`.
- Optimizes all `.wasm` files with `wasm-opt` and `wasm-strip`.
- Generates `.sha256` checksum files for all `.wasm` files.
- Moves built files to a `builds` directory.

## Installation

Here's an example GitHub action workflow that builds applicable packages and creates a GitHub
release when a tag is pushed.

```yaml
name: Release

permissions:
contents: write

on:
push:
tags:
- 'v[0-9]+*'
pull_request:

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: moonrepo/setup-rust@v1
with:
cache: false
targets: wasm32-wasi
- uses: moonrepo/build-proto-plugin@v0
- if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
uses: ncipollo/release-action@v1
with:
artifacts: builds/*
artifactErrorsFailBuild: true
prerelease:
${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') ||
contains(github.ref_name, '-rc') }}
skipIfReleaseExists: true
```

## Configuring packages

Packages to be built and published must have the following configuration in their `Cargo.toml`:

- The `package.publish` setting should be omitted or set to `true`.
- The `lib.crate-type` setting should be set to `cdylib`.

```toml
[package]
name = "example_plugin"
version = "1.2.3"
edition = "2021"
license = "MIT"

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

Furthermore, this action will inherit the `profile.release.opt-level` setting from the current
package (or workspace root) `Cargo.toml`. This setting will be passed to `wasm-opt`. We suggest `s`
for file size, or `z` for runtime speed.

```toml
[profile.release]
codegen-units = 1
debug = false
lto = true
opt-level = "s"
panic = "abort"
```
9 changes: 1 addition & 8 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
name: 'Build proto plugin'
author: 'Miles Johnson'
description: 'Build, optimize, and prepare a proto WASM plugin for release.'
# inputs:
# auto-install:
# default: false
# description: 'Auto-install tools on setup.'
# outputs:
# cache-key:
# description: 'The cache key used for the toolchain folder (~/.proto).'
description: 'Build, optimize, and prepare proto WASM plugins for release.'
runs:
using: 'node20'
main: 'dist/index.js'
Expand Down
117 changes: 68 additions & 49 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-await-in-loop */

import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
Expand Down Expand Up @@ -56,6 +58,10 @@ async function installWabt() {
core.addPath(path.join(extractedDir, `wabt-${WABT_VERSION}/bin`));
}

function getRoot(): string {
return process.env.GITHUB_WORKSPACE!;
}

async function findBuildablePackages() {
core.info('Finding buildable packages in Cargo workspace');

Expand All @@ -75,6 +81,7 @@ async function findBuildablePackages() {
}

interface Manifest {
package?: { publish?: boolean };
profile?: Record<string, { 'opt-level'?: string }>;
}

Expand All @@ -83,34 +90,43 @@ async function findBuildablePackages() {
).stdout;

const builds: BuildInfo[] = [];
const metadata = JSON.parse(output.trim()) as Metadata;

await Promise.all(
metadata.packages.map(async (pkg) => {
if (!metadata.workspace_members.includes(pkg.id)) {
core.debug(`Skipping ${pkg.name}, not a workspace member`);
return;
const metadata = JSON.parse(output) as Metadata;

const rootManifest = TOML.parse(
await fs.promises.readFile(path.join(getRoot(), 'Cargo.toml'), 'utf8'),
) as Manifest;

metadata.packages.forEach((pkg) => {
if (!metadata.workspace_members.includes(pkg.id)) {
core.info(`Skipping ${pkg.name}, not a workspace member`);
return;
}

core.info(`Found ${pkg.name}, loading manifest ${pkg.manifest_path}, checking targets`);

const manifest = TOML.parse(fs.readFileSync(pkg.manifest_path, 'utf8')) as Manifest;
const publish = manifest.package?.publish ?? true;

if (!publish) {
core.info(`Skipping ${pkg.name}, not publishable`);
return;
}

pkg.targets.forEach((target) => {
if (target.crate_types.includes('cdylib')) {
core.info(`Has cdylib lib target, adding build`);

builds.push({
optLevel:
manifest.profile?.release?.['opt-level'] ??
rootManifest.profile?.release?.['opt-level'] ??
's',
packageName: pkg.name,
targetName: target.name,
});
}

core.debug(`Found ${pkg.name}, loading manifest ${pkg.manifest_path}, checking targets`);

const manifest = TOML.parse(
await fs.promises.readFile(pkg.manifest_path, 'utf8'),
) as Manifest;

pkg.targets.forEach((target) => {
if (target.crate_types.includes('cdylib')) {
core.debug(`Found cdylib target, adding build`);

builds.push({
optLevel: manifest.profile?.release?.['opt-level'] ?? 's',
packageName: pkg.name,
targetName: target.name,
});
}
});
}),
);
});
});

core.info(`Found ${builds.length} builds`);

Expand All @@ -128,12 +144,11 @@ async function hashFile(filePath: string): Promise<string> {
async function buildPackages(builds: BuildInfo[]) {
core.info(`Building packages: ${builds.map((build) => build.packageName).join(', ')}`);

const root = process.env.GITHUB_WORKSPACE!;
const buildDir = path.join(root, 'builds');
const buildDir = path.join(getRoot(), 'builds');

await fs.promises.mkdir(buildDir);

core.debug(`Building (mode=release, target=wasm32-wasi)`);
core.info(`Building all (mode=release, target=wasm32-wasi)`);

await exec.exec('cargo', [
'build',
Expand All @@ -142,36 +157,40 @@ async function buildPackages(builds: BuildInfo[]) {
...builds.map((build) => `--package=${build.packageName}`),
]);

await Promise.all(
builds.map(async (build) => {
core.debug(`Optimizing ${build.packageName} (level=${build.optLevel})`);
for (const build of builds) {
core.info(`Optimizing ${build.packageName} (level=${build.optLevel})`);

const fileName = `${build.targetName}.wasm`;
const inputFile = path.join(root, 'target/wasm32-wasi/release', fileName);
const outputFile = path.join(buildDir, fileName);
const fileName = `${build.targetName}.wasm`;
const inputFile = path.join(getRoot(), 'target/wasm32-wasi/release', fileName);
const outputFile = path.join(buildDir, fileName);

await exec.exec('wasm-opt', [`-O${build.optLevel}`, inputFile, '--output', outputFile]);
await exec.exec('wasm-strip', [outputFile]);
core.debug(`Input: ${inputFile}`);
core.debug(`Output: ${outputFile}`);

core.debug(`Hashing ${build.packageName} (checksum=sha256)`);
await exec.exec('wasm-opt', [`-O${build.optLevel}`, inputFile, '--output', outputFile]);
await exec.exec('wasm-strip', [outputFile]);

const checksumFile = `${outputFile}.sha256`;
const checksumHash = await hashFile(outputFile);
core.info(`Hashing ${build.packageName} (checksum=sha256)`);

await fs.promises.writeFile(checksumFile, checksumHash);
const checksumFile = `${outputFile}.sha256`;
const checksumHash = await hashFile(outputFile);

core.info(`${build.packageName} (${checksumHash})`);
core.info(`--> ${outputFile}`);
core.info(`--> ${checksumFile}`);
}),
);
await fs.promises.writeFile(checksumFile, checksumHash);

core.info(`${build.packageName} (${checksumHash})`);
core.info(`--> ${outputFile}`);
core.info(`--> ${checksumFile}`);
}
}

async function run() {
try {
await Promise.all([installWabt(), installBinaryen()]);
const builds = await findBuildablePackages();

await buildPackages(await findBuildablePackages());
if (builds.length > 0) {
await Promise.all([installWabt(), installBinaryen()]);
await buildPackages(builds);
}
} catch (error: unknown) {
core.setFailed(error as Error);
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@moonrepo/build-proto-plugin",
"version": "0.0.5",
"description": "A GitHub action to build, optimize, and prepare a proto WASM plugin for release.",
"version": "0.1.0",
"description": "A GitHub action to build, optimize, and prepare proto WASM plugins for release.",
"main": "dist/index.js",
"scripts": {
"build": "ncc build ./index.ts",
Expand Down