Dart + GitHub Actions: Publish your command-line tools for Windows/macOS/Linux into GitHub Releases
Tags: dart, githubactions
Published: 2022-04-02 06:29:10

---
title: Dart + GitHub Actions: Publish your command-line tools for Windows/macOS/Linux into GitHub Releases
published: true
description: 
tags: Dart, GitHubActions
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n5a3y26wmp8b7sblncs.png
---

## Motivation

Dart is very suitable for building quick-and-dirty CLI tools and share them among your collegue, since it produces a single binary with `dart compile exe`.

It would be useful if organization-internal tools are published in GitHub Releases like this:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n5a3y26wmp8b7sblncs.png)

Unfortunately, [Dart doesn't support cross-compile](https://github.com/dart-lang/sdk/issues/28617) at this moment and **we have to compile the source code on each platform (Windows, macOS, Linux)** before publishing the binaries, while Golang supports cross-compile and [GoReleaser](https://goreleaser.com/) already made it easy to publish the binaries for each platform.

GitHub Actions provides Windows/macOS/Linux build environments for free and it would be really useful for compiling and publishing the CLI tools composed with Dart.

This article shows how to configure the GitHub Actions.

## GitHub Actions for build

As many articles already says, only 4 step are required for compiling the Dart code.

```yml
- uses: actions/checkout@v2
- uses: dart-lang/setup-dart@v1
- run: dart pub get
- run: dart compile exe bin/mycli.dart -o mycli
```

One point to consider is that the output binary should be named as mycli **.exe** on Windows and mycli (without extension) on macOS/Linux.
We can solve the naming by defining the matrix like below:

```yml
jobs:
  compile:
    name: dart compile exe
    strategy:
      matrix:
        include:
          - runs-on: ubuntu-latest
            binary-name: mycli_linux_amd64
          - runs-on: macos-latest
            binary-name: mycli_macos_amd64
          - runs-on: windows-latest
            binary-name: mycli_windows.exe
    runs-on: ${{ matrix.runs-on }}
    steps:
      - uses: actions/checkout@v2
      - uses: dart-lang/setup-dart@v1
      - run: dart pub get
      - run: mkdir ${{ matrix.runs-on }}
      - run: dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}
```

## GitHub Actions for release

https://docs.github.com/en/rest/reference/releases

According to this reference, GitHub Release requires 2 steps for publishing some binaries:

* Create a release resource
  * `POST https://api.github.com/repos/{owner}/{repo}/releases`
* Upload the binaries into the release
  * `POST https://uploads.github.com/repos/{owner}/{repo}/releases/{release_id}/assets`

### Use action-gh-release for simple uploading

[action-gh-release](https://github.com/softprops/action-gh-release) really makes it easy to do these steps.

```yml
- uses: softprops/action-gh-release@v1
  with:
    draft: true
    files: out/*
```

With this simple definition, GitHub Actions will upload all files in `out/` into a new draft release.

```yml
    files: |
      out/mycli_linux
      out/mycli_macos
      out/mycli_windows
```

Also `files` can be specified to individual file list instead of glob expression.

## Combine the build process with the release process using artifact

![build and release](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyczrfy03wv2abphbgy5.png)

Each of 3 build actions generate a binary and release process should upload them all at once. The flow can be implemented with GitHub artifact.

```yml
name: Publish

on:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+'

jobs:
  compile:
    name: dart compile exe
    strategy:
      matrix:
        include:
          - runs-on: ubuntu-latest
            binary-name: mycli_linux_amd64
          - runs-on: macos-latest
            binary-name: mycli_macos_amd64
          - runs-on: windows-latest
            binary-name: mycli_windows.exe
    runs-on: ${{ matrix.runs-on }}
    steps:
      - uses: actions/checkout@v2
      - uses: dart-lang/setup-dart@v1
      - run: dart pub get
      - run: mkdir ${{ matrix.runs-on }}
      - run: dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}
      - uses: actions/upload-artifact@v2
        with:
          name: bin-${{ matrix.runs-on }}
          path: ${{ matrix.runs-on }}

  release:
    needs: compile
    name: github release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: bin-ubuntu-latest
          path: bin-linux
      - uses: actions/download-artifact@v2
        with:
          name: bin-macos-latest
          path: bin-macos
      - uses: actions/download-artifact@v2
        with:
          name: bin-windows-latest
          path: bin-windows
      - uses: softprops/action-gh-release@v1
        with:
          draft: true
          files: bin-*/*
```

This works expectedly like below:

![flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwyvq5hpaw10fzo31rky.png)

and the artifacts are successfully uploaded to GitHub Releases! :)

![Released](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kg70ttdrqii5o2up4ngq.png)