├── .gitignore ├── test-build.sh ├── .prettierrc.json ├── test └── test.go ├── tsconfig.json ├── test-binaryen.sh ├── src ├── utils.ts ├── tinygo │ ├── sys.ts │ └── install.ts ├── binaryen │ ├── sys.ts │ └── install.ts └── index.ts ├── check-version.sh ├── action.yml ├── package.json ├── LICENSE ├── .github └── workflows │ ├── check-dist.yml │ └── validate.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /test-build.sh: -------------------------------------------------------------------------------- 1 | tinygo build -o wasm.wasm -target wasm ./test/test.go 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello tinygo") 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test-binaryen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Checks that Binaryen is not installed 4 | if [ -x "$(command -v wasm-opt)" ]; then 5 | echo 'Error: Binaryen is installed.' >&2 6 | exit 1 7 | fi 8 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import cp from 'child_process'; 3 | 4 | export function printCommand(command: string) { 5 | const output = cp.execSync(command).toString(); 6 | core.info(output); 7 | } 8 | -------------------------------------------------------------------------------- /src/tinygo/sys.ts: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | 3 | export function getPlatform(): string { 4 | const platform = os.platform(); 5 | return platform === 'win32' ? 'windows' : platform; 6 | } 7 | 8 | export function getArch(): string { 9 | const arch = os.arch(); 10 | return arch === 'x64' ? 'amd64' : arch; 11 | } 12 | -------------------------------------------------------------------------------- /check-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$1" ]; then 4 | echo "Must supply tinygo version argument" 5 | exit 1 6 | fi 7 | 8 | tinygo_version="$(tinygo version)" 9 | echo "Found tinygo version '$tinygo_version'" 10 | if [ -z "$(echo $tinygo_version | grep $1)" ]; then 11 | echo "Unexpected version" 12 | exit 1 13 | fi 14 | -------------------------------------------------------------------------------- /src/binaryen/sys.ts: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | 3 | const platformMap: Record = { 4 | win32: 'windows', 5 | darwin: 'macos', 6 | }; 7 | 8 | export function getPlatform(): string { 9 | const platform = os.platform(); 10 | return platformMap[platform] ?? platform; 11 | } 12 | 13 | export function getArch(platform: string): string { 14 | const arch = os.arch(); 15 | switch (arch) { 16 | case 'arm64': 17 | return platform === 'linux' ? 'aarch64' : 'arm64'; 18 | case 'x64': 19 | return 'x86_64'; 20 | default: 21 | return arch; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup TinyGo' 2 | description: 'Set up a specific version of TinyGo and add it to the PATH' 3 | author: acifani 4 | branding: 5 | icon: 'play' 6 | color: 'blue' 7 | inputs: 8 | tinygo-version: 9 | description: 'The exact TinyGo version to download and use.' 10 | default: '0.30.0' 11 | install-binaryen: 12 | description: 'Whether you want to install Binaryen or not.' 13 | default: 'true' 14 | binaryen-version: 15 | description: 'The exact Binaryen version to download and use.' 16 | default: '116' 17 | runs: 18 | using: 'node20' 19 | main: 'dist/index.js' 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup-tinygo", 3 | "version": "1.0.0", 4 | "description": "Setup TinyGo for GitHub actions", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "tsc && ncc build", 8 | "format": "prettier --write **/*.ts" 9 | }, 10 | "keywords": [], 11 | "author": "Alessandro Cifani ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@actions/core": "^1.11.1", 15 | "@actions/tool-cache": "^2.0.2" 16 | }, 17 | "devDependencies": { 18 | "@tsconfig/node20": "^20.1.2", 19 | "@types/node": "^20.8.9", 20 | "@vercel/ncc": "^0.38.3", 21 | "prettier": "^3.5.3", 22 | "typescript": "^5.8.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import { installBinaryen } from './binaryen/install'; 3 | import { installTinyGo } from './tinygo/install'; 4 | 5 | setup(); 6 | 7 | async function setup() { 8 | try { 9 | const tinyGoVersion = core.getInput('tinygo-version'); 10 | core.info(`Setting up tinygo version ${tinyGoVersion}`); 11 | await installTinyGo(tinyGoVersion); 12 | 13 | const shouldInstallBinaryen = core.getInput('install-binaryen'); 14 | if (shouldInstallBinaryen === 'false') { 15 | core.info('Skipping binaryen installation'); 16 | } else { 17 | const binaryenVersion = core.getInput('binaryen-version'); 18 | core.info(`Setting up binaryen version ${binaryenVersion}`); 19 | await installBinaryen(binaryenVersion); 20 | } 21 | } catch (error: any) { 22 | core.setFailed(error.message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alessandro Cifani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/check-dist.yml: -------------------------------------------------------------------------------- 1 | name: Check dist/ 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | check-dist: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Set Node.js 20.x 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20.x 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Rebuild the dist/ directory 30 | run: npm run build 31 | 32 | - name: Compare the expected and actual dist/ directories 33 | run: | 34 | if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then 35 | echo "Detected uncommitted changes after build. See status below:" 36 | git diff 37 | exit 1 38 | fi 39 | id: diff 40 | 41 | # If index.js was different than expected, upload the expected version as an artifact 42 | - uses: actions/upload-artifact@v4 43 | if: ${{ failure() && steps.diff.conclusion == 'failure' }} 44 | with: 45 | name: dist 46 | path: dist/ 47 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | verify: 10 | name: Verify setup 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - macos-latest 17 | - ubuntu-latest 18 | - ubuntu-24.04-arm 19 | - windows-latest 20 | # - windows-11-arm - Binaryen does not provide binaries for Windows on ARM as of v123 21 | tinygo: ['0.36.0', '0.37.0'] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version: '1.24' 27 | 28 | - name: setup-tinygo ${{ matrix.tinygo }} 29 | uses: ./ 30 | with: 31 | tinygo-version: ${{ matrix.tinygo }} 32 | binaryen-version: '123' 33 | 34 | - name: Verify version 35 | run: ./check-version.sh ${{ matrix.tinygo }} 36 | shell: bash 37 | 38 | - name: Test build 39 | run: ./test-build.sh 40 | shell: bash 41 | 42 | no-binaryen: 43 | name: No Binaryen 44 | # using macos because ubuntu has binaryen pre-installed 45 | # TO-DO: implement a stable testing method 46 | runs-on: macos-latest 47 | steps: 48 | - uses: actions/checkout@v4 49 | - uses: actions/setup-go@v5 50 | with: 51 | go-version: '1.24' 52 | 53 | - name: setup-tinygo 54 | uses: ./ 55 | with: 56 | tinygo-version: '0.37.0' 57 | install-binaryen: 'false' 58 | 59 | - name: Binaryen not installed 60 | run: ./test-binaryen.sh 61 | shell: bash 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # setup-tinygo 2 | 3 | [![Check dist/](https://github.com/acifani/setup-tinygo/actions/workflows/check-dist.yml/badge.svg)](https://github.com/acifani/setup-tinygo/actions/workflows/check-dist.yml) 4 | [![Validate](https://github.com/acifani/setup-tinygo/actions/workflows/validate.yml/badge.svg)](https://github.com/acifani/setup-tinygo/actions/workflows/validate.yml) 5 | 6 | This actions sets up a TinyGo environment for GitHub Actions. 7 | 8 | ## Usage 9 | 10 | ### Basic 11 | 12 | ```yaml 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: acifani/setup-tinygo@v2 16 | with: 17 | tinygo-version: '0.30.0' 18 | ``` 19 | 20 | ### With matrix expansion 21 | 22 | ```yaml 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | tinygo: ['0.29.0', '0.30.0'] 29 | name: TinyGo ${{ matrix.tinygo }} 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: acifani/setup-tinygo@v2 33 | with: 34 | tinygo-version: ${{ matrix.tinygo }} 35 | ``` 36 | 37 | ### With custom Go version 38 | 39 | TinyGo needs Go and, by default, this action will use whatever 40 | version is available in the runner. If you want to control the 41 | Go version, you can use `actions/setup-go` before `acifani/setup-tinygo` 42 | 43 | ```yaml 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions/setup-go@v2 47 | with: 48 | go-version: 1.21 49 | - uses: acifani/setup-tinygo@v2 50 | with: 51 | tinygo-version: '0.30.0' 52 | ``` 53 | 54 | ### With custom Binaryen version 55 | 56 | This action will install [Binaryen](https://github.com/WebAssembly/binaryen) 57 | which is needed for building WASM on Windows and MacOS. 58 | You can customize the version with the dedicated input value 59 | 60 | ```yaml 61 | steps: 62 | - uses: actions/checkout@v2 63 | - uses: acifani/setup-tinygo@v2 64 | with: 65 | tinygo-version: '0.30.0' 66 | binaryen-version: '116' 67 | ``` 68 | 69 | ### Without Binaryen 70 | 71 | If you don't need Binaryen, you can omit the installation 72 | 73 | ```yaml 74 | steps: 75 | - uses: actions/checkout@v2 76 | - uses: acifani/setup-tinygo@v2 77 | with: 78 | tinygo-version: '0.30.0' 79 | install-binaryen: 'false' 80 | ``` 81 | -------------------------------------------------------------------------------- /src/binaryen/install.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as io from '@actions/io'; 3 | import * as tool from '@actions/tool-cache'; 4 | import path from 'path'; 5 | import { printCommand } from '../utils'; 6 | import { getArch, getPlatform } from './sys'; 7 | 8 | const toolName = 'binaryen'; 9 | const platform = getPlatform(); 10 | const arch = getArch(platform); 11 | 12 | export async function installBinaryen(version: string): Promise { 13 | const installPath = await extract(version); 14 | return addToPath(installPath, version); 15 | } 16 | 17 | async function extract(version: string): Promise { 18 | core.debug(`Checking cache for binaryen v${version} ${arch}`); 19 | const cachedDirectory = tool.find(toolName, version, arch); 20 | if (cachedDirectory) { 21 | // tool version found in cache 22 | return cachedDirectory; 23 | } 24 | 25 | core.debug(`Downloading binaryen v${version} for ${platform} ${arch}`); 26 | try { 27 | const downloadPath = await download(version); 28 | const extractedPath = await extractArchive(downloadPath); 29 | const cachedPath = await tool.cacheDir( 30 | extractedPath, 31 | toolName, 32 | version, 33 | arch, 34 | ); 35 | 36 | return cachedPath; 37 | } catch (error: any) { 38 | throw new Error(`Failed to download version ${version}: ${error}`); 39 | } 40 | } 41 | 42 | async function download(version: string): Promise { 43 | const downloadURL = `https://github.com/WebAssembly/binaryen/releases/download/version_${version}/binaryen-version_${version}-${arch}-${platform}.tar.gz`; 44 | core.debug(`Downloading from ${downloadURL}`); 45 | const downloadPath = await tool.downloadTool(downloadURL); 46 | core.debug(`Downloaded binaryen release to ${downloadPath}`); 47 | return downloadPath; 48 | } 49 | 50 | async function extractArchive(downloadPath: string): Promise { 51 | return tool.extractTar(downloadPath); 52 | } 53 | 54 | async function addToPath(installDir: string, version: string) { 55 | const binaryen = `binaryen-version_${version}`; 56 | core.info(`Adding ${installDir}/${binaryen}/bin to PATH`); 57 | core.addPath(path.join(installDir, binaryen, 'bin')); 58 | const found = await io.findInPath('wasm-opt'); 59 | core.debug(`Found in path: ${found}`); 60 | const wasmopt = await io.which('wasm-opt'); 61 | printCommand(`${wasmopt} --version`); 62 | } 63 | -------------------------------------------------------------------------------- /src/tinygo/install.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as io from '@actions/io'; 3 | import * as tool from '@actions/tool-cache'; 4 | import path from 'path'; 5 | import { printCommand } from '../utils'; 6 | import { getArch, getPlatform } from './sys'; 7 | 8 | const toolName = 'tinygo'; 9 | const arch = getArch(); 10 | const platform = getPlatform(); 11 | 12 | export async function installTinyGo(version: string): Promise { 13 | const installPath = await extract(version); 14 | return addToPath(installPath); 15 | } 16 | 17 | async function extract(version: string): Promise { 18 | core.debug(`Checking cache for tinygo v${version} ${arch}`); 19 | const cachedDirectory = tool.find(toolName, version, arch); 20 | if (cachedDirectory) { 21 | // tool version found in cache 22 | return cachedDirectory; 23 | } 24 | 25 | core.debug(`Downloading tinygo v${version} for ${platform} ${arch}`); 26 | try { 27 | const downloadPath = await download(version); 28 | const extractedPath = await extractArchive(downloadPath); 29 | const cachedPath = await tool.cacheDir( 30 | extractedPath, 31 | toolName, 32 | version, 33 | arch, 34 | ); 35 | 36 | return cachedPath; 37 | } catch (error: any) { 38 | throw new Error(`Failed to download version ${version}: ${error}`); 39 | } 40 | } 41 | 42 | async function download(version: string): Promise { 43 | const extension = platform === 'windows' ? 'zip' : 'tar.gz'; 44 | const downloadURL = `https://github.com/tinygo-org/tinygo/releases/download/v${version}/tinygo${version}.${platform}-${arch}.${extension}`; 45 | core.debug(`Downloading from ${downloadURL}`); 46 | const downloadPath = await tool.downloadTool(downloadURL); 47 | core.debug(`Downloaded tinygo release to ${downloadPath}`); 48 | return downloadPath; 49 | } 50 | 51 | async function extractArchive(downloadPath: string): Promise { 52 | let extractedPath = ''; 53 | if (platform === 'windows') { 54 | extractedPath = await tool.extractZip(downloadPath); 55 | } else { 56 | extractedPath = await tool.extractTar(downloadPath); 57 | } 58 | 59 | return extractedPath; 60 | } 61 | 62 | async function addToPath(installDir: string) { 63 | core.info(`Adding ${installDir}/tinygo/bin to PATH`); 64 | core.addPath(path.join(installDir, 'tinygo', 'bin')); 65 | const found = await io.findInPath('tinygo'); 66 | core.debug(`Found in path: ${found}`); 67 | const tinygo = await io.which('tinygo'); 68 | printCommand(`${tinygo} version`); 69 | printCommand(`${tinygo} env`); 70 | } 71 | --------------------------------------------------------------------------------