🦜

Goのビルドにて部分的にファイルを差し替える方法

2023/10/27に公開

おおまかな仕組み解説

go buildには-overlayというオプションがあります。
このオプションを活用することで、ビルド時に読み込むソースコードを部分的に指しかえることができます。

overlay.json
{
  "Replace":{
    "${GOPATH}/pkg/mod/github.com/username/example@v0.0.1/lib.go":"./local/lib.go"
  }
}
./local/lib.go
<元ファイルの複製を改変した内容>

以上のようなファイルを用意しておき、

go build -overlay overlay.json .

そうするとgithub.com/username/example/lib.goの代わりに./local/lib.goを使ってビルドされます。

参照パスを明らかにする方法

go build -x .というように-xオプションをつけるとコンパイル時に参照しているファイルを表示することができます。

もし、ビルド済みオブジェクトがキャッシュに残っていて見つからない場合は
go build -a -x .というように-aオプションも付けるとよいでしょう。
そうやって特定したパスをoverlay.jsonのキー側に設定すると成功しやすいです。
(特にWindowsの場合、\\という表記が重要)

実例サンプル

wails init -n sample -t https://github.com/nobonobo/wails-sveltekit-skeleton
cd sample

ありがちなフレームレス+独自タイトルバーをSkeletonで作ってみる。
独自タイトルバーを<div style="--wails-draggable: drag">タグで囲んでおくとタイトルバーをドラッグすることでウインドウ移動が可能になります。
閉じるボタンアイコンは以下のサイトから引用しました。

http://svgicons.sparkk.fr/

frontend/src/routes/+layout.svelte
<script>
  import { AppBar } from "@skeletonlabs/skeleton";
  import { LightSwitch } from "@skeletonlabs/skeleton";
  import "../app.postcss";
</script>

<div style="--wails-draggable: drag">
  <AppBar>
    <svelte:fragment slot="lead">Sample</svelte:fragment>
    <svelte:fragment slot="trail"
      ><LightSwitch /><button on:click={runtime.Quit}
        ><svg class="w-4 h-4" viewBox="0 0 20 20">
          <path
            class="fill-black dark:fill-white"
            fill="none"
            d="M11.469,10l7.08-7.08c0.406-0.406,0.406-1.064,0-1.469c-0.406-0.406-1.063-0.406-1.469,0L10,8.53l-7.081-7.08
c-0.406-0.406-1.064-0.406-1.469,0c-0.406,0.406-0.406,1.063,0,1.469L8.531,10L1.45,17.081c-0.406,0.406-0.406,1.064,0,1.469
c0.203,0.203,0.469,0.304,0.735,0.304c0.266,0,0.531-0.101,0.735-0.304L10,11.469l7.08,7.081c0.203,0.203,0.469,0.304,0.735,0.304
c0.267,0,0.532-0.101,0.735-0.304c0.406-0.406,0.406-1.064,0-1.469L11.469,10z"
          />
        </svg></button
      ></svelte:fragment
    >
  </AppBar>
</div>
<slot />

また、以下のAlwaysOnTopオプションをtrueにしておきます。

main.go
21:	err := wails.Run(&options.App{
...
31:		Frameless:         true,
32:		AlwaysOnTop:       true,
33:		StartHidden:       false,

これでwails devで開発版の実行をします。

Windows向けのフォーカスを奪わないアプリ

この一般のウインドウをクリックするとフォーカスを奪ってしまいます。「オンスクリーンキーボード」の様なアプリを作りたい場合、フォーカスを奪わないアプリにする必要があります。

そういったウインドウを作るためのパラメータがWailsには備わっていません。
Windowsの場合、Win32APIのSetWindowPosの最後の属性パラメータにSWP_NOACTIVATEがあればフォーカスを奪わなくなります。

なので、それに関係する実装を改変します。

overlay.json
{
  "Replace": {
    "C:\\Users\\<user>\\go\\pkg\\mod\\github.com\\wailsapp\\wails\\v2@v2.6.0\\internal\\frontend\\desktop\\windows\\window.go": "./overlay/window.go"
  }
}

リプレースに使うキーにはGoのソースパスを指定するのですが、Goモジュールモードにおけるソースパスの構成は以下の通り。

標準パッケージ

<GOROOT>/src/<サブパッケージパス>/<ファイル名.go>

v1までのモジュール

<GOPATH>/pkg/mod/<モジュール名>@<バージョンタグ>/<サブパッケージパス>/<ファイル名.go>

v2以降のパス切っているモジュール

<GOPATH>/pkg/mod/<モジュール名>/v2@<バージョンタグ>/<サブパッケージパス>/<ファイル名.go>

このパスのWindows版はダブルバックスラッシュで区切ったものになります。

github.com/wailsapp/wails/internal/frontend/desktop/windows/window.goをoverlayフォルダに複製します。
その137行目にw32.SWP_NOACTIVATEを属性に追加します。

overlay/window.go
137: 	w32.SetWindowPos(handle, w32.HWND_TOPMOST, 0, 0, 0, 0, w32.SWP_NOACTIVATE|w32.SWP_NOSIZE|w32.SWP_NOMOVE|w32.SWP_NOSENDCHANGING)

これでinternal配下のファイルの挙動を改変することができます。

オーバーレイオプション付きのビルド

wails buildとすると通常デフォルトアイコンをつけてビルドしてくれます。
いったんそのようにビルドしておき、以下のコマンドでビルドしなおします。

go build -overlay overlay.json -tags desktop,production -ldflags "-w -h -H windowsgui" -o build/bin/sample.exe .

これでフォーカスを奪わずに常に最上位に居座るウインドウによるアプリケーションが実現できます。

アイコンが実行ファイルにバンドルされない問題

ただし、アイコンが付与されないので以下のツールを利用して、
rsrc_windows_386.sysorsrc_windows_amd64.sysoといったファイルをプロジェクトルートに出力しておきます。

その状態で上記のビルドをする場合、アイコンが実行ファイルにバンドルされます。

まとめ

  • 対応プラットフォームそれぞれで同じ挙動が得られるものでないと本家に取り込んでもらうのは難しい
  • 自環境特化のパッチを当てるためにフォークするのは大げさ
  • 特定ファイルのみの修正で済むのなら-overlayオプションが使える

Discussion