GoでマルチプラットフォームなOSSを開発する時の注意点

5.5K Views

September 16, 25

スライド概要

profile-image

Fellow at Henry, Inc. Tech SaaSのPdM、スタートアップ取締役CTOや外資スタートアップのIC等を経て現任。好きな言語はGoとPerlと中国語で雑なOSSを200以上量産している。3 times ISUCON winner. 著書「みんなのGo言語」共著他。Podcast: https://oss4.fun/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Goでマルチプラットフォー ムOSS開発する際の注意点 layerx.go #2 - 2025/09/16

2.

Profile ● ● ● id: Songmu (ソンムー) Masayuki Matsuki / 松木雅幸 Blog: おそらくはそれさえも平凡な日々 ○ ● ● ● http://www.songmu.jp/riji/ 好きな言語は、PerlとGoと中国語 3 Times ISUCON Winner OSS作家 ○ ○ ○ 200+ OSS Developer 入門監視 付録C 執筆 「みんなのGo言語」共著者

3.

OSS作家として 色々なOSSを作ったり、継続メンテナンスしています。 自分で作った物で1000stars超えた物はまだ残念ながら無い。 ● x-motemen/ghq (3.3k stars) ○ ● k1LoW/deck (825 stars) ○ ○ ● ソースコードリポジトリ管理ツール MarkdownからGoogle Slideを生成するツール このスライドはdeckで作成されています tagpr (248 stars) ○ GitHub上でのリリースエンジニアリング民主化のためのカスタムGitHub Action

4.

最近の新作OSS AIにより捗っている。 ● chape ○ ○ ● laminate ○ ○ ● 文字列の画像化管理ツール deck でコード文字列の変換の際に便利 action-push-to-another-repository ○ ● MP3のチャプターやメタデータ編集ツール Podcastの編集などで便利 (使って欲しい!) あるリポジトリの内容を別リポジトリにpushするGitHub Action action-create-branch ○ ブランチを作成するGitHub Action

5.

Goはマルチプラットフォーム対応が簡単 ● ● ● ● クロスビルドが簡単でマルチプラットフォームに展開できる ただ、単にビルドし分けるだけで良いかと言うと不十分なことも多い OSSを使ってもらいたいからちゃんとサポートしたい 今回はcliツールの場合

6.

ドメインエキスパートに頼る ブログ発信やライブラリ作って下さっていてありがてぇ…。 ● Windows ○ ○ ● mattnさん Big Sky Plan 9 ○ ○ lufia / plan9user さん Plan 9とGo言語のブログ

7.

「私はPlan 9対応をサボっています」の札 を首から下げています… ● Goにとって大事なOSだし、Goは当然Plan 9をサポートしている ○ ● Go作者の一人のRob Pike氏がPlan 9の作者でもある でも Russ Coxさんも常用はしていないみたいだし… ○ ○ 元々Bell研でPlan 9の開発をしていて、その後GoogleでGoの開発を主導 ■ regexpパッケージや、Go Modules等の開発 以下のインタビューが面白かった ■ Uses This / Russ Cox

8.

Agenda ● ファイルシステム ○ ○ ● 外部コマンドとシェル ○ ○ ● path/filepath XDG Base Directory Specification 外部コマンド実行のお作法 環境差異の吸収 端末(ターミナル)入出力 ○ 色付け

9.

ファイルシステム

11.

区切り文字を適切に使う ● os.PathSeparator (ファイルパスの区切り文字) ○ ○ ● windowsでは \ それ以外は / os.PathListSeparator (環境変数のPATHなどの区切り文字) ○ ○ ○ windowsでは ; plan9 では NULL文字 それ以外は : PATHの分割をする場合は filepath.SplitList(os.Getenv("PATH")) と する。これは内部的にちゃんと os.PathListSeparator を使っている。

12.

ファイルパスの処理 ● ● ● filepath.Join (複数のパス要素を結合) filepath.Clean (パスを正規化)) filepath.ToSlash (パス区切り文字を / に変換) filepath.Clean は ちゃんと os.PathSeparator で区切ったパスを返して くれるが、Windowsはパス区切りが / でもちゃんと認識してくれるので、 filepath.ToSlash で / に変換し直した方が使いやすいこともある。

13.

ファイル配置 これらのファイルをどこに置くか。 ● ● ● ● 設定ファイル データファイル キャッシュファイル 状態保持ファイル

14.

ホームディレクトリ取得 環境変数 $HOME を見るのではなく os.UserHomeDir() を使う。 ● os.UserHomeDir() の処理内容 ○ ○ ○ 基本は $HOME 環境変数 plan9では $home 環境変数、windowsでは %USERPROFILE% 環境変数 iosでは "/" ディレクトリ、 android では "/sdcard" ディレクトリ

15.

XDG Base Directory Specification ● ● ● https://specifications.freedesktop.org/basedir-spec/ XDG は "Cross(X) Desktop Group" の略 LinuxやUnix系OSでの設定ファイル等の配置場所の規約

16.

"dot files" 乱立問題 ● ● https://help.gnome.org/misc/release-notes/3.6/admins-xdg.html.en ホームディレクトリ直下のdotfileから XDG_CONFIG を使う流れに ○ 徐々に移行が進んでいる Unix-like systems have traditionally lacked a standard way to store application or user data on a per-user basis. Consequently these data are often stored in an ad-hoc, inconsistent, and problematic way in "dot files" of the user's home directory. freedesktop.org therefore issued a recommended solution for this problem.

17.

XDG Base Directory Specificationの歴史 結構地道に更新されている。 https://specifications.freedesktop.org/ バージョン リリース年 0.6 2003 最初のリリース 0.7 2010 マイナーチェンジ 0.8 2021 XDG_STATE_HOME の追加

18.

主要な XDG Base Directory これらのディレクトリ配下に、アプリケーション名のディレクトリを作成して、その 中にファイルを配置する。 ex. ~/.config/git/config 環境変数名 デフォルト値 用途 類似 XDG_CONFIG_HOME ~/.config 設定ファイル /etc XDG_CACHE_HOME ~/.cache キャッシュデータ /var/cache XDG_DATA_HOME ~/.local/share データファイル /usr/share XDG_STATE_DIR ~/.local/state 状態ファイル /var/lib

19.

Goにおける特殊ディレクトリ取得 ● os.UserConfigDir() ○ ○ ○ ○ ● windowsでは %AppData% Macでは ~/Library/Application Support Plan 9 では $home/lib その他Unixでは $XDG_CONFIG_HOME または ~/.config os.UserCahceDir() ○ ○ ○ ○ windowsでは %LocalAppData% Macでは ~/Library/Caches Plan 9 では $home/lib/cache その他Unixでは $XDG_CACHE_HOME または ~/.cache

20.

MacとXDG Base Directory Macの os.UserConfigDir() や os.UserCacheDir() は ~/Library 以下 のディレクトリを返し、 XDG Base Directory は使われない。 ただ、私は自作cliツールの場合、Macでそれらは使っておらず、独自で XDG Base Directory に従うようにしている。 ● ● テキストでの設定ファイル管理 (いわゆる dotfiles管理) をやりやすい コマンドラインツールではそちらの方が多数派

21.

MacでのApp固有ディレクトリを掘る場合 AppStoreで配布する場合は必須。ちゃんとしたデスクトップアプリの場合も 使うと良いか。 ● ~/Library/Application Support/{{BundleID}} のようにディレ クトリを掘る ○ Bundle IDは reverse-DNS format (ex. com.example.myapp) を使う

22.

WindowsではXDG Base Directoryを使うべ きか ● 迷っている ○ ● Windowsでの XDG Base Directory 準拠は一般的ではない ○ ● 使わないのがよいのではないかと思いつつあるが、対応をサボっている .config などの . がWindowsのファイルシステム上扱いづらい Windows Subsystem for Linux (WSL) ではlinux同様に XDG Base Directoryを使って良い ○ というか、 runtime.GOOS が "linux" になるので普通に使われるはず

23.

Windowsでの配置ディレクトリ規約 Windowsには %APPDATA% と %LOCALAPPDATA% という2つの環境変数がある ● %AppDdata% は基本 C:\Users\<User>\AppData\Roaming ○ ○ ● 同じユーザーの他のPCでも同期したいデータ os.UserConfigDir() はこれを返す %LocalAppData% は基本 C:\Users\<User>\AppData\Local ○ ○ そのPC固有のデータ ■ キャッシュや状態ファイル os.UserCacheDir() はこれを返す

24.

WindowsでのApp固有ディレクトリ ● ● "{{Vendor}}\{{AppName}}" や "{{Vendor}} {{AppName}}" のよう に Vendor と AppName を含めたディレクトリを掘るのが一般的 例: ○ ○ \Google\Chrome \Microsoft VS Code

25.

Windowsでの各種ファイルやディレクトリの配置案 以下のようにするのはどうだろうか?識者の意見を聞きたい。 ● 設定ファイル ○ ○ ● Dataディレクトリ ○ ○ ● os.UserConfigDir() (%AppData%) 配下のサブディレクトリ ex. %AppData%\Songmu\MyCLI\data\ Cacheディレクトリ ○ ○ ● os.UserConfigDir() (%AppData%) 配下 ex. %AppData%\Songmu\MyCLI\config.yaml os.UserCacheDir() (%LocalAppData%) 配下のサブディレクトリ ex. %LocalAppData%\Songmu\MyCLI\cache\ Stateディレクトリ ○ ○ os.UserCacheDir() (%LocalAppData%) 配下のサブディレクトリ ex. %LocalAppData%\Songmu\MyCLI\state\

26.

github.com/spf13/pathologize ● クロスプラットフォームにまたがった、ファイルパスの無害化をしてくれ るライブラリ ○ ○ ● ● 何らかの入力文字列を元にファイル名やディレクトリ名を生成する場合に必須 そもそもそんなことをやるべきではないが 最近 github.com/Songmu/laminate で利用 去年出来たばかりのライブラリでタグもまだ打たれていない ○ ○ 作者の spf13 さんは "Hugo" や "cobra" の作者なので信頼がおける リリースして欲しいのでissue上げた ■ https://github.com/spf13/pathologize/issues/2

27.

実コード引用 ● ● トラバーサルや特殊記号以外にも予約されているファイル名も弾くように Windowsは今でも "CON" や "CON.txt" のようなファイル名は作れない ○ Windowsに限らずマルチプラットフォームで作れないようにしておいた方が安心

28.

外部コマンド実行とシェル

29.

外部コマンドは極力文字列で受けとらない 極力配列で受け取るべし。 ● ● コマンドの場合: $ yourcli -opt -- exe arg1 arg2 設定ファイル等の場合: "cmd": ["exe", "arg1", "arg2"] これだと、このまま exec.Command("exe", "arg1", "arg2) とできる。

30.

double dash (--) convention ● 元々は、コマンドの引数の終端を意味するガイドライン ○ ● Ref. https://www.gnu.org/software/bash/manual/bash.html 別コマンドを実行するラッパーコマンドにおける外部コマンド指定時に良 く使われる ○ ○ 元々のコマンドへの引数か、指定コマンドの引数かを区別するため ■ 自明な場合は省略可能なコマンドも (例えば bundle exec) 例: ■ docker run -- ls l ■ bundle exec -- rspec

31.

やむを得ず、文字列で受け取る場合 $ yourcli -c 'ls -al' → シェル経由で実行する必要がある exec.Command("sh", "-c", cmdStr) など

32.

自分で分割して実行してはダメ シェルに渡さずに自分で分割しているケースもたまに見かけるがやってはダ メ。 ● ● args := strings.Field(cmdStr) のようなことをしてはいけない mattn/go-shellwords 等を使った分割も確実ではない 例えば、"ls|grep -v foo" の様にパイプを含む場合、さらにパイプの前後 にスペースがない場合などに上手く動かない。 結局、シェルの字句解析を再実装するのと同じ難易度がある。シェルによる文 法の違いもあるので、それを吸収するのは実質不可能。

33.

シェルを経由しなくても良い場合 渡されたコマンド文字列が /^[-_a-zA-Z0-9](\.[a-zA-Z0-9]+)?+$/ の ような単純なコマンド名らしき場合は、シェルを経由せずに exec.Command(cmdStr) で実行しても良い。

34.

(余談) mattn/go-shellword の READMEのThanks https://github.com/mattn/go-shellwords 文字列をシェル引数的分割をするmattnさん製のGoのライブラリ。 ● ● ● 556 stars 以前はDocker内部でも使われていた Parse::CommandLine の移植と書かれているが…

36.

(余談) mattnさんの爆速移植 ● 2014年のある日のとあるチャットルーム ○ ○ 11:45に紹介して、12:03 にリポジトリ公開 実に20分かからず移植。 11:45:13 Songmu: https://metacpan.org/pod/Parse::CommandLine 11:47:55 mattn_ jp: 移植するなら Parse::CommandLine の方が楽そう 11:49:35 Songmu: まあ、一文字一文字ですからね。 11:49:39 Songmu: 割と適当だけど 11:57:09 mattn_ jp: 移植出来た。テスト書くか。 12:03:28 mattn_ jp: 出来た 12:04:36 mattn_ jp: https://github.com/mattn/go-shellwords 12:05:29 Songmu: hayai

37.

シェルを探す必要がある 単に sh -c だとWindowsで動かない可能性がある。 以下は、あくまで一例。GitHub Actions同様にbashを優先的に探したり、Windows 環境を考慮して、powershellやcmdも探している。これらは全て -c オプションでコ マンド文字列を受けとれる。

38.

シェルが無い環境もあるよ! コンテナ実行用環境には、コンテナサイズやセキュリティリスク低減の為に シェルが入っていない環境もある。なので、シェル経由でコマンド実行出来な い環境もあることに注意。 ● ● chainguard/static Google Distroless Chainguardはglibcをサポートした最近有力な軽量コンテナイメージ群。

39.

端末(ターミナル)入出力

40.

出力の色付け ● ● カラフルなコマンドラインツールが増えた ANSIエスケープシーケンスを使った色付け ○ ● github.com/fatih/color が定番 これだけだとWindows上では動かない

42.

色付けを避けた方が良いケース 以下の対応ができると丁寧。 ● NO_COLOR 環境変数が設定されている場合 ○ ● https://no-color.org 出力がターミナルではなくパイプやリダイレクトで繋がっている場合 ○ ○ ファイル書き出しにはエスケープシーケンスは不要 パイプの場合は、less -R の様に PAGERが色付けに対応している場合もあるので好みは ある ■ 個人的にはパイプでもオフにしても良い ■ 必要なら grep や git log のように --color=always オプションを設ける

43.

色付けをハンドリングするコード例 github.com/mattn/go-isatty がWindowsもケアしてくれて便利。

44.

端末入出力を制御したい場合 ● github.com/mattn/go-tty が環境差異を吸収してくれて便利 ○ ● mattn無双 golang.org/x/sys/windows も準公式ライブラリで便利です

45.

まとめ

46.

やりたい範囲でやりましょう ● ● ● こういう調べものをして、対応するのは結構楽しい 利用者が増えてくれるのも嬉しい ただ、自分が用意しづらい環境のデバッグをするのが辛いことも ○ ○ 環境差異を吸収してくれるライブラリを利用する ■ ありがてぇ 自分の負担にならない範囲でやりましょう ■ 自分が使わないコードが沢山あるとつらくなることもあります

47.

スポンサー募集 色々OSSを開発・メンテナンスしています。$1のワンショットでも嬉しいの で、活動を支援してくれると嬉しいです。 ● GitHub Sponsor ○ ● https://github.com/sponsors/Songmu ghq handbook ○ ○ https://leanpub.com/ghq-handbook https://zenn.dev/songmu/books/ghq-handbook