CLI for Windows with Go
Tags: go, windows
Published: 2020-12-13 04:49:02

---
title: CLI for Windows with Go
published: true
description: 
tags: go, windows
//cover_image: https://direct_url_to_image.jpg
---

Recently, I created a CLI for windows. I learned about some TIPS. Let's describe that in this article. Sample code exists in https://github.com/y-yagi/cli-for-windows

## "GUI binary" or "console binary"

By default, Go build a binary as "console binary". This means a window console is kept showing when a binary is executed. It is good for applications that will finish immediately. But that's not good for some kind of applications(e.g. GUI application, HTTP server).

In the case of such applications, we can build binary as "GUI binary". In "GUI binary", a window console doesn't show when a binary is executed.

In order to build with "GUI binary", you need to specify "-H windowsgui" to ldflags.
On Windows, -H windowsgui writes a "GUI binary" instead of a "console binary."

```bash
$ go build -ldflags "-H windowsgui"
```

See also: https://github.com/golang/go/blob/3b2a578166bdedd94110698c971ba8990771eb89/src/cmd/link/doc.go#L28

## Embed icon to a binary file

Maybe you want to include an icon in your CLI. Go build binary as a [`Portable Executable`](https://en.wikipedia.org/wiki/Portable_Executable) format. Portable Executable format has a section for resources(rsrc section). We need to add an icon to that section.

When embed data to Go binary, need to prepare `syso` file. There is a tool called [rsrc](https://github.com/akavel/rsrc) that will generate `syso` file for embedding in the rsrc section, so let's use that.

```bash
$ go get github.com/akavel/rsrc
$ rsrc -ico your-icon.ico -arch=amd64
```

You need to prepare `syso` files per architecture. `syso` file will link automatically when run `go build`. So we don't need to specify options or files to `go build`.

See also: [GcToolchainTricks · golang/go Wiki](https://github.com/golang/go/wiki/GcToolchainTricks#use-syso-file-to-embed-arbitrary-self-contained-c-code)

## Notification Area

If you choose "GUI binary" applications, maybe we need to consider how the exit of applications.
In the case of "console binary", if want to exit, simplify close the console. But in the case of "GUI binary", we can't do that.
Of course, we can exit applications via the Task Manager. But maybe it difficult for people who unfamiliar with Windows.

So let's consider to use "Notification Area"(or called "System Tray").

The notification area is located at the right end of the taskbar, it can be operated to applications by GUI.

Of course, already exists that library for using Notification Area.
[getlantern/systray](https://github.com/getlantern/systray)

Here's an example code with `systray`.

```go
func main() {
	systray.Run(onReady, onExit) // (1)
	return
}

func onReady() {
	systray.SetIcon(icon) // (2)
	systray.SetTitle("My CLI")
	systray.SetTooltip("My CLI")

	mQuit := systray.AddMenuItem("Quit", "Quit") // (3)
	<-mQuit.ClickedCh
	systray.Quit()
}

func onExit() {
  // clean up
}
```

(1) `systray.Run` initializes GUI and starts the event loop. Also, you can pass two functions that run when applications starting and exiting.
(2) `systray.SetIcon` accept to array bytes of icon data. This means you need to prepare array bytes of an icon. [2goarray](https://github.com/cratonica/2goarray) can use in this case. `2goarray` can encode files into array bytes.

```bash
$ go get github.com/cratonica/2goarray
$ 2goarray icon main < icon.png > icon.go
```

(3) `systray.AddMenuItem` add a menu item to an application. In this example, add a menu item that quit an application.

This example shows as follows on Windows.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jp9amrt3tooiqg44h100.png)

## CI

Of course, we need to consider the CI. Before, we have little choice for CI. But now we have some choices. For example, [AppVeyor](https://www.appveyor.com/), [CircleCI](https://circleci.com/build-environments/windows/), and [GitHub Actions](https://github.co.jp/features/actions).

In my personal opinion, if you are using GitHub, I recommend GitHub Actions. Because it can use easily and don't' need to create an extra account.

An example of GitHub Actions is the following. It needs to add a file to `.github/workflows/` directory for use(e.g. `.github/workflows/ci.yml`).

```yml
name: CI
on: [push]
jobs:

  build:
    name: Build
    runs-on: windows-latest
    steps:

    - name: Set up Go # (1)
      uses: actions/setup-go@v2
      with:
        go-version: 1.15

    - name: Check out code # (2)
      uses: actions/checkout@v2

    - name: Run test (3)
      run: |
        go test ./...
      env:
        GO111MODULE: on
```

(1) Set up Go environment by [setup-go](https://github.com/actions/setup-go) action.
(2) Checks-out my repository by [checkout](https://github.com/actions/checkout) action.
(3) Run tests.

That's it!

If you want to run `golangci-lint` in CI, you can use [golangci-lint-action](https://github.com/golangci/golangci-lint-action) same as other actions.

## Release

Finally, let's consider the management of release binaries. If you are using GitHub, I think [GitHub Releases](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-releases) is good for that. It can manage source and binaries in the same place.

GitHub Releases are based on Git tags. So I wanted to upload binaries to GitHub Releases when push gi tags. We can use GitHub Actions also this case. Here is an example of a setting.


```yml
name: Release
on:
  push:
    tags:
      - 'v*' # (1)

jobs:
  windows:
    runs-on: windows-latest
    steps:
      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: 1.15

      - name: Check out code into the Go module directory
        uses: actions/checkout@v2

      - name: Run release # (2)
        uses: goreleaser/goreleaser-action@v2
        with:
          version: latest
          args: release --rm-dist
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

(1) Restrict that only run when git tag started with `v`(e.g. `v1.0.0`).
(2) Use [GoReleaser](https://goreleaser.com/) action for upload binaries to GitHub Releases.

`GoReleaser` settings as follows.

```yml
builds:
  - binary: "MY CLI"
    goos:
      - windows
    ldflags: -H windowsgui
    goarch:
      - amd64
      - 386
archives:
  -
    format: zip
release:
  github:
    owner: y-yagi
    name: cli-for-windows
```

You need to specify `github` section under the `release` section` for upload binaries to GitHub Releases.