Goで作るexeファイルにアイコンとかライセンス情報とか git の tag から得たバージョンなどを埋め込んでみる 

### これまでのあらすじ

+ [<続> goで作るexeファイルにアイコンを埋め込んでみる (Windows) #Go - Qiita](https://qiita.com/zetamatta/items/dd63a2ff4d01fdb6709a)
+ [goで作るexeファイルにアイコンを埋め込んでみる (Windows) #Go - Qiita](https://qiita.com/zetamatta/items/4aa36661336a56b95e06)

最近、これらに「いいね」をいただいたようなのですが、Go言語による代替ツールがあり、自分は現在そちらを使っているので、内容をアップデートしたいと思います。

### josephspurrier/goversioninfo の概要

- [josephspurrier/goversioninfo: Golang Microsoft Windows File Properties/Version Info and Icon Resource Generator](https://github.com/josephspurrier/goversioninfo)

Go言語で作られたリソース生成ツールです。こちらは

- RCファイルではなく、お馴染みの JSON ファイルで情報を指定する
- Windows のプロパティーで参照できる情報（ファイルバージョン・プロダクトバージョンを全部指定できる）
- アイコンを複数指定することができる（EXEファイルを直接表示する時はデフォルトになるが、ユーザがショートカットを作る際はどれかを選ぶことができる）

など多機能かつ使いやすくなっています。

### josephspurrier/goversioninfo の使い方

こんな感じで呼び出します。

```sh
go run github.com/hymkor/goversioninfo/cmd/goversioninfo@master
      -icon=nyagos.ico,nyagos32x32.ico,nyagos16x16.ico
      -o nyagos.syso v.json
```
（スクロールして読みづらいので改行していますが、実際に使う時は１行にしてください）

+ これでアイコンなどのリソース情報を格納した .syso ファイルが生成される  
  → `go build` する時のディレクトリ上に置いておくと、取り込まれる
+ アイコンは `-icon=` オプションで指定する
+ バージョン情報はコマンドラインオプションでも指定できるが、その他もろものライセンス情報などもプロパティーに載せたい場合も多いので、次のように JSON で指定するとよい

```v.json
{
	"FixedFileInfo":
	{
		"FileVersion": {
			"Major": 4,
			"Minor": 4,
			"Patch": 17,
			"Build": 0
		},
		"ProductVersion": {
			"Major": 4,
			"Minor": 4,
			"Patch": 17,
			"Build": 0
		},
		"FileFlagsMask": "3f",
		"FileFlags ": "00",
		"FileOS": "040004",
		"FileType": "01",
		"FileSubType": "00"
	},
	"StringFileInfo":
	{
		"Comments": "",
		"CompanyName": "NYAOS.ORG",
		"FileDescription": "Nihongo Yet Another GOing Shell",
		"FileVersion": "4.4.17_0-20-g3a8c748a",
		"InternalName": "",
		"LegalCopyright": "Copyright (C) 2014-2025 HAYAMA_Kaoru",
		"LegalTrademarks": "",
		"OriginalFilename": "NYAGOS.EXE",
		"PrivateBuild": "",
		"ProductName": "Nihongo Yet Another GOing Shell",
		"ProductVersion": "4.4.17_0-20-g3a8c748a",
		"SpecialBuild": ""
	},
	"VarFileInfo":
	{
		"Translation": {
			"LangID": "0411",
			"CharsetID": "04E4"
		}
	}
}
```

### 結果の説明

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29454/82d74ad0-95f6-48f4-a39d-3e8d61766a28.png)

+ アイコンは左上に出ていますね
+ ファイルバージョンは個別ファイルのバージョン、製品バージョンは一度にリリースするファイルセット全体に対して付けられるバージョンだと考えればよいでしょう
+ バージョンには二つの表現があります
    + 一つは文字列表現。主に人間が目視で確認するためにあり、RC1 とか β とか、そういう語句を付加できます
    + もう一つは整数４つからなる表現。こちらはプログラムが大小関係などを比較する場合に使える形となっています
+ Windows のプロパティー画面では両方表示されるわけではなく、次のように表現が選ばれています
    + ファイルバージョンは整数表現
    + 製品バージョンは文字列表現

### git tag との連携

Go言語ではセマンティックバージョンを git のタグで管理するのが主流です。バージョンを二カ所で管理するのは冗長になるので、できればそちらからバージョン情報を取り込んでEXEファイルのリソースへ反映できると便利です。

で、汎用的なものではないんですが、昔、自分のプロダクト用にそういうものを作りました。これは `git describe --tags` を内部から呼び出して、その情報を埋め込んだ JSON を標準出力に出すという `go run` 用のスクリプトです。

```go
package main

import (
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

var jsonTemplate = `{
	"FixedFileInfo":
	{
		"FileVersion": {
			"Major": %d,
			"Minor": %d,
			"Patch": %d,
			"Build": %d
		},
		"ProductVersion": {
			"Major": %d,
			"Minor": %d,
			"Patch": %d,
			"Build": %d
		},
		"FileFlagsMask": "3f",
		"FileFlags ": "00",
		"FileOS": "040004",
		"FileType": "01",
		"FileSubType": "00"
	},
	"StringFileInfo":
	{
		"Comments": "",
		"CompanyName": "NYAOS.ORG",
		"FileDescription": "Nihongo Yet Another GOing Shell",
		"FileVersion": "%s",
		"InternalName": "",
		"LegalCopyright": "Copyright (C) 2014-2025 HAYAMA_Kaoru",
		"LegalTrademarks": "",
		"OriginalFilename": "NYAGOS.EXE",
		"PrivateBuild": "",
		"ProductName": "Nihongo Yet Another GOing Shell",
		"ProductVersion": "%s",
		"SpecialBuild": ""
	},
	"VarFileInfo":
	{
		"Translation": {
			"LangID": "0411",
			"CharsetID": "04E4"
		}
	}
}
`

var de = regexp.MustCompile(`[-\._]`)

func versionStrToNum(versionString string) ([]int, error) {
	v := de.Split(versionString, -1)
	if len(v) < 4 {
		return nil, fmt.Errorf("%s: too short version string", versionString)
	}

	var vn [4]int
	var err error

	if vn[0], err = strconv.Atoi(v[0]); err != nil {
		return nil, fmt.Errorf("%s: invalid major version", versionString)
	}
	if vn[1], err = strconv.Atoi(v[1]); err != nil {
		return nil, fmt.Errorf("%s: invalid minor version", versionString)
	}
	if vn[2], err = strconv.Atoi(v[2]); err != nil {
		return nil, fmt.Errorf("%s: invalid patch version", versionString)
	}
	if vn[3], err = strconv.Atoi(v[3]); err != nil {
		return nil, fmt.Errorf("%s: invalid build version", versionString)
	}
	return vn[:], nil
}

func getVersionData() (string, []int, error) {
	bin, err := exec.Command("git", "describe", "--tags").Output()
	if err != nil {
		return "", nil, fmt.Errorf("Could not get version string from git (%w)", err)
	}
	str := strings.TrimSpace(string(bin))
	num, err := versionStrToNum(str)
	return str, num, err
}

func main() {
	fileVerStr, fileVerNum, err := getVersionData()
	if err != nil {
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(1)
	}

	fmt.Printf(jsonTemplate,
		fileVerNum[0],
		fileVerNum[1],
		fileVerNum[2],
		fileVerNum[3],
		fileVerNum[0],
		fileVerNum[1],
		fileVerNum[2],
		fileVerNum[3],
		fileVerStr,
		fileVerStr)
}
```

自分は、これを他のソースの中に次のような `go generate` 行を入れて、最終的に必要となる .syso ファイルを作成しています。

```
//go:generate cmd /c go1.20.14.exe run mkversioninfo.go > v.json &&
    go1.20.14.exe run github.com/hymkor/goversioninfo/cmd/goversioninfo@master
    -icon=nyagos.ico,nyagos32x32.ico,nyagos16x16.ico
    -o ..\nyagos.syso v.json && del v.json
```
（スクロールして読みづらいので改行していますが、実際に使う時は１行にしてください）


ライセンスとか著作権とかハードコーディングされているので、そういうのを適当に書き換えて使っていただければ…と思います
（元のコードは BSD License 扱いにしていますし）