- 投稿日:2019-02-09T20:40:06+09:00
[go-astilectron]D&DでGoモジュール側にファイルパスを受け渡す
GoでGUIアプリを作ってみたいと思い、go-astilectronでDrag and DropでファイルパスをGoのモジュール側に受け渡してみたのでメモ
まだastilectron自体にはD&Dをハンドリングするような機能は実装されていない(2019/02時点)ようだったので、少々泥臭い方法で実装してみた
所感
go-astilectronで作って
go build
すれば単一のバイナリに固められるので、すごく便利
しかし、以下の点だけは少々気になった…
- JavaScript側で
astilectron
オブジェクトを扱う必要があったが、その仕様が調べても出てこない
- Devtoolを有効にすれば持っている関数名は調べられるが、当該関数をどのように呼び出すべきかがわからない…
処理の流れ
HTML, JavaScriptでdropイベントを検出
->astilectron.sendMessage()
でD&Dされたファイルのパスを文字列として受け渡す
-> Goモジュール側で文字列をパースするディレクトリ構成
% tree . -L 2 drag_n_drop ├── Gopkg.lock ├── Gopkg.toml ├── drag_n_drop ├── main.go ├── static │ ├── index.html │ └── main.js └── vendor ├── astilectron ├── astilectron-v0.30.0.zip ├── electron-linux-amd64 ├── electron-linux-amd64-v4.0.1.zip ├── github.com ├── golang.org └── status.jsonソースコード
Golang
package main import ( "fmt" "strings" ast "github.com/asticode/go-astilectron" ) func main() { var a, _ = ast.New(ast.Options{}) defer a.Close() a.Start() var w, _ = a.NewWindow("static/index.html", &ast.WindowOptions{ Center: ast.PtrBool(true), Height: ast.PtrInt(600), Width: ast.PtrInt(600), }) w.Create() w.Show() w.OnMessage(func(m *ast.EventMessage) interface{} { // Unmarshal var s string m.Unmarshal(&s) pathes := strings.Split(s, ";") fmt.Println(pathes) return nil }) a.Wait() }HTML
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div id="dropzone">Drop files here!</div> <script src="./main.js"></script> </body> </html>JavaScript
const elemDrop = document.getElementById('dropzone'); elemDrop.addEventListener('dragover', function(event) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; }); elemDrop.addEventListener('drop', function(ev) { event.preventDefault(); files = event.dataTransfer.files; pathes = [] for (let i=0; i<files.length; i++) { // ディレクトリやその他MIMEタイプが取れないものは弾く if (files[i].type === "") { continue } pathes.push(files[i].path); } // ファイルパスを";"で連結してastilectron側に渡す astilectron.sendMessage(pathes.join(';')) });
- 投稿日:2019-02-09T15:49:04+09:00
goの静的解析系ツールをGitHub Actionsで動かしてみた
Golangの静的解析ツールをGitHub Actionsで動かして、PRに通知してみた。GitHubTokenとかあまり考えなくていいからその辺の管理が楽そうですごくいい印象を受けた。
ただ、まだGithubActionsが理解できていない部分が多く、不備は多々ありそう・・・
TerraformのGithubActionsをかなり参考にした。(ほぼgolangに書き換えただけ)
hashicorp/terraform-github-actions
ほんと、ありがとうございますTL;DL
結論から言うと、こんな感じファイルを
.github/main.workflow
に置くだけ。.github/main.workflowworkflow "Golang Test Workflow" { on = "pull_request" resolves = [ "go imports", "go vet", "staticcheck", ] } action "filter to pr open synced" { uses = "actions/bin/filter@master" args = "action 'opened|synchronize'" } action "go imports" { uses = "grandcolline/golang-github-actions/imports@v0.1.2" needs = "filter to pr open synced" secrets = ["GITHUB_TOKEN"] } action "go vet" { uses = "grandcolline/golang-github-actions/vet@v0.1.2" needs = "filter to pr open synced" secrets = ["GITHUB_TOKEN"] env = { FLAGS = "-shadow" } } action "staticcheck" { uses = "grandcolline/golang-github-actions/staticcheck@v0.1.2" needs = "filter to pr open synced" secrets = ["GITHUB_TOKEN"] }これで、こんな感じでPRに通知してくれる。
少し詳細
上記のワークフローの中で、下記のレポジトリを呼び出して、そのレポジトリ内にあるDockerfileから、Dockerコンテナを起動し、テストを実行している。
grandcolline/golang-github-actions
なので実際は、上記のレポジトリの部分を実装していった。
(ただ、実装といっても、静的解析ツールを入れたDockerfileと、テスト実行のシェルスクリプトのentrypoint.shを用意するだけ・・・)例えば、goimportsを動かすジョブは、
goimports/DockerfileFROM golang:latest LABEL "com.github.actions.name"="go imports" LABEL "com.github.actions.description"="Run goimports" LABEL "com.github.actions.icon"="terminal" LABEL "com.github.actions.color"="purple" LABEL "repository"="https://github.com/grandcolline/golang-github-actions" LABEL "homepage"="https://github.com/grandcolline/golang-github-actions" LABEL "maintainer"="grandcolline <grandcolline@gmail.com>" RUN apt-get update && \ apt-get -y install jq && \ go get -u golang.org/x/tools/cmd/goimports COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
jq
とgoimports
を取得するDockerfileを用意する。
LABELなどの仕様は、GitHubActionsの仕様に従う。詳しくは、ここ。実行するシェルは、こんな感じ。
goimports/entrypoint.sh#!/bin/sh set -e cd "${GO_ACTION_WORKING_DIR:-.}" set +e UNFMT_FILES=$(sh -c "goimports -l . $*" 2>&1) test -z "${UNFMT_FILES}" SUCCESS=$? echo "${UNFMT_FILES}" set -e if [ ${SUCCESS} -eq 0 ]; then exit 0 fi if [ "${GO_ACTION_COMMENT}" = "1" ] || [ "${GO_ACTION_COMMENT}" = "false" ]; then exit ${SUCCESS} fi FMT_OUTPUT="" for file in ${UNFMT_FILES}; do FILE_DIFF=$(goimports -d -e "${file}" | sed -n '/@@.*/,//{/@@.*/d;p}') FMT_OUTPUT="${FMT_OUTPUT} <details><summary><code>${file}</code></summary> \`\`\`diff ${FILE_DIFF} \`\`\` </details> " done COMMENT="## goimports Failed ${FMT_OUTPUT} " PAYLOAD=$(echo '{}' | jq --arg body "${COMMENT}" '.body = $body') COMMENTS_URL=$(cat /github/workflow/event.json | jq -r .pull_request.comments_url) curl -s -S -H "Authorization: token ${GITHUB_TOKEN}" --header "Content-Type: application/json" --data "${PAYLOAD}" "${COMMENTS_URL}" > /dev/null exit ${SUCCESS}自動でカレントディレクトリ(
/github/workspace
)にソースコードがcheckoutさてれいるので、goimports -l .
でフォーマットがおかしいファイル一覧を取得し、ファイル毎にdiffを${FMT_OUTPUT}
に整形しながら入れてあげて、最後にcurlでPRに通知する。
${GITHUB_TOKEN}
などは、workflow側から入れてあげれば、自然な形で環境変数として使える。環境変数などはここをみながら。
- 投稿日:2019-02-09T15:45:39+09:00
GitHub Link Card Creatorがカッコいいのでnpm scriptsに組み込む
はじめに
みんなにOSSを見てもらいたい人の為に、GitHubリポジトリのOGP的画像を自動生成してくれるサービスを作った
こちらの記事で紹介されているGitHub Link Card Creatorが素晴らしくカッコいいので、node.jsのnpm scriptsに組み込む方法を模索してみました。
対象とするユーザー
- Go?なにそれ?
- 普段はnode.jsを使っている。
- ターミナルを触ったことがある。
- 自作のGitHubリポジトリにリンクカードをつけたい。
- macユーザーである。
この記事の環境
この記事は以下の環境を想定しています。各ソフトのバージョンが異なると、記事の内容は適用できない場合があります。ご注意ください。
- macOS 10.14.3
- node 8.11.4
- go 1.11.5 darwin/amd64
- Homebrew 2.0.0
この記事で解消したい問題
GitHub Link Card CreatorにはオフィシャルのWebアプリケーションがあります。
GitHub Link Card CreatorこちらのWebアプリケーションを利用すれば、画像の生成からリンクコードの出力までが一気にできます。
しかし、出力された画像URLがQiitaでは直接利用できないという問題があります。(参考 :issue#4)現状では、生成された画像をQiitaの記事内にアップロードし、生成されたリンクURLを書き換えることで対応が可能です。
しかしQiitaに画像をアップロードしてしまうと、カード情報の更新のたびに再アップロードが必要になります。この問題を解消するため
- GitHub Link Card Creatorをローカル環境で動かす。
- npm scriptsのタスクに組み込む。
- 生成された画像をGitHub Pagesにプッシュする。
- Qiitaの記事から画像を読み込む。
という組み込み作業を行ってみます。
Goとは
GoはGoogleが主導して開発しているプログラム言語およびその環境です。
正式な名称はGoですが、golangと呼ばれることもあります。
開発環境はオープンソースで、パッケージをインストールすればマルチプラットフォームで動作します。設定
macOS環境で、Homebrewを経由してGoパッケージをインストールします。
Homebrewのインストール
Homebrew
HomebrewはmacOS用のパッケージマネージャーです。Homebrewからさまざまなパッケージをインストールできます。
すでにHomebrewを導入している人は、この項目をスキップしてください。このスクリプトをターミナルに入力すると、Homebrewがインストールされます。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"インストールが成功したか確認するためにバージョンを表示してみます。
brew -v Homebrew 2.0.0
バージョン番号が表示されたら無事インストール成功です。
Go環境の構築
インストール
先ほどインストールしたHomebrewを利用して、Goのパッケージをインストールします。
brew install go
パッケージのサイズが100MB以上ありますので、少し処理に時間がかかります。ゆっくりお待ちください。
go version go version go1.11.5 darwin/amd64こちらもバージョン情報が表示できればインストール成功です。
GitHub Link Card Creatorのインストール
GitHub Link Card Creatorパッケージは、以下のコマンドでインストールできます。
go get github.com/po3rin/github_link_creator/cmd/repoimgホームディレクトリ直下のgoフォルダーの中にファイルが保存されていれば、インストールは成功です。
パスの設定
GitHub Link Card Creatorはターミナルからコマンド
repoimg
で呼び出すことができます。
このコマンドが通るように、ターミナルにパスを通す必要があります。このパスはホームディレクトリ直下の
.bash_profile
というファイルに保存されています。
このファイルに以下の2行のパスを追加します。export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/binターミナルから
.bash_profile
を編集する方法はこちらの記事をご参照ください。
MacでPATHを通すまた、Finderとお好きなテキストエディターを使って編集することもできます。
Finderから「移動」→「フォルダへ移動…」を選択し
~/.bash_profile
へ移動します。
ここで表示されたファイルをお好きなテキストエディターで編集してください。ターミナルの再起動
.bash_profile
の変更はそのままでは反映されません。再読み込みのコマンドを実行するか、ターミナルを再起動する必要があります。再読み込みのコマンドは以下の通りです
source ~/.bash_profile
.bashrcや.bash_profileなどの変更設定をすぐに反映させたい
WebStormやVS Codeなどのターミナルを内包しているソフトも、再起動をする必要があります。
以下のようなエラーが出る場合は、
.bash_profile
の反映ができていません。ソフトやmacの再起動を試してみてください。bash: repoimg: command not found
ここまでの作業でGitHub Link Card Creatorがターミナルから呼び出せるようになりました。
repoimg -n <GitHubのユーザー名>/<リポジトリ名>
で画像が生成されれば成功です。
GitHub Pagesの設定
次に、GitHub Pagesの公開設定を行います。
まずは作成済みのリポジトリのWebページにアクセスし、Settingsを開きます。
次に、GitHub Pagesの設定項目に移動し、Sourceをmaster Branch /docs folder
に変更します。これでリポジトリの
./docs
以下がhttps://<ユーザー名>.github.io/<リポジトリ名>/
でアクセスできます。npm scriptsに統合
最後に、npm scriptsに
repoimg
コマンドを組み込みます。package.json"scripts": { "doc:card": "repoimg -n <GitHubのユーザー名>/<リポジトリ名> -o ./docs/card.png" }このスクリプトで
./docs/card.png
が生成されます。
このファイルをプッシュすると以下のURLでアクセスができます。https://<ユーザー名>.github.io/<リポジトリ名>/card.pngこのURLをGitHub Link Card Creatorで生成される埋め込みコードに組み込むと
無事Qiitaの記事からGitHub Pagesの画像ファイルが読み込めました!以上、ありがとうございました。
- 投稿日:2019-02-09T12:03:11+09:00
a[:0] と append の秘密
(http://zetamatta.hatenablog.com/entry/2019/02/05/161003 より転載)
io.Reader のプリプロセッサな io.Reader を作るの中で、"tidwall/transform"で使われていると紹介したテク:
a = a[:0]
は領域のサイズをリセットするが、a = make([]T,0,cap(a))
と違って、使っていたメモリブロックを再利用するため、allocation 回数を削減できるというものだった。だが、旧
a
の領域が他で使われていないかを気にせず、無頓着に使うと append でおかしいことになる。たとえばa := []string{ "a","b","c" } b := a[:0] b = append(b,"1") fmt.Printf("%+v\n",a)とすると a の内容が [1 b c] となる。これは append が領域を上書きで使用するためだ。
append 側で他所で使われているかチェックしてくれたらよさそうな話ではあるが、おそらく物理的に無理だろう。というのも参照カウンタ方式ではなく、マーク&スイープ方式の Garbage Collector を使っている場合、参照されている場所が自分を含めて2個以上あるか、調べようがないのだ。(GCの中身のコードならばできなくもないだろうが…)
となると、常に他で使われているという前提で append を実装しないと、上のような状況は避けられないわけだ。そういう append ちょっと作ってみよう。
package main import ( "fmt" ) func appendStr(a []string, b string) []string { r := make([]string, len(a)+1) copy(r,a) r[len(a)] = b return r } func main() { a := []string{} for i := 0; i < 10; i++ { a = appendStr(a, fmt.Sprintf("%d", i)) } fmt.Printf("%+v\n", a) }こんな実行効率わるそうなの、みんな使いたいと思うだろうか?結局、みんなオリジナル append を使うことになるだろう?(でも、スクリプト言語では効率が悪くても平気でやってそうではある)。
こういった append の挙動は一見奇異なものに見え「Go言語は糞」と罵られる一因ではある。だが、C言語を知る者は append は realloc と対応する関数だから、領域拡張前の a が正常に使える保証はなく、使うべきではないと直感的に分かっている人が多い。
人によって、Go言語の評価が「サイコー」or「糞」と正反対になるのは、そういうあまり言及されない前提知識や慣れ(訓練されたC言語プログラマはエラーチェックコードが多くてもまったく気にしない等)の有無が大きいのではないだろうか。
- 投稿日:2019-02-09T03:43:01+09:00
素人が低層から理解するGo Web開発(2)【TCPsocketのソースを覗く】
このシリーズについて
- ド素人が
- GoによるWeb開発を
- どのコードもブラックボックスにならず
- プロトコル, ネットワークの流れもふわっと分かり
- 実際にWebアプリをデプロイする
過程を記録していきます。これで第2回目です。
前回までと今回
前回(第1回)はsocket を使って低レイヤを意識しながらHTTPリクエストを送ってみようということで
- インターネット層
- トランスポート層
- socket
の概念を流し見して socket を送るコードが何をしているのかぼや~っと把握しました。
今回(第2回)はコードに用いられているインターフェース、構造体の中身を見ていこうと思います。
ResolveTCPAddr
前回の最後では、リクエストを送るプログラムの1行目
tcpAddr, err := net.ResolveTCPAddr("tcp4", "almi.tokyo:80") checkError(err)「
ResolveTCPAdrr
ってなんだ?」って話でしたね。中身を覗いていきましょう。場所はsrc\net\tcpsock.go
です。func ResolveTCPAddr(network, address string) (*TCPAddr, error) { switch network { case "tcp", "tcp4", "tcp6": case "": // a hint wildcard for Go 1.0 undocumented behavior network = "tcp" default: return nil, UnknownNetworkError(network) } addrs, err := DefaultResolver.internetAddrList(context.Background(), network, address) if err != nil { return nil, err } return addrs.forResolve(network, address).(*TCPAddr), nil }return : *TCPAddr
まずこの関数が何を返すのか見てみましょう。返り値の型は
*TCPAddr
, つまりTCPAddr
のポインタです。多分TCPアドレスのことでしょう。TCPAddrの定義
同じく
src\net\tcpsock
の中で定義されています。楽でタスカル。// TCPAddr represents the address of a TCP end point. type TCPAddr struct { IP IP Port int Zone string // IPv6 scoped addressing zone }コメントを見ると TCP エンドポイントを表現する構造体だ、とされていますね。
つまりトランスポート層が責任を持つ範囲の一番端の住所を表現します。前回見たとおり、IP address(ここでは IP で定義されてるやつかな) にポート番号を付け加えたものとなっていますね。この画像で言えば IP によってインターネット層が各端末(サーバ、スマホ、デスクトップ)の位置を識別します(オレンジの枠)。
そしてポート番号によって、端末上のどのアプリケーションのどのプロセスか(HTML, Amazon, Twitter, Chrome, Evernote...)を識別します(青の枠)。
結果的にIP + port番号 は TCPAddress とみなせることになります。
IPv6対応のために(?)
Zone
が追加されていますが、stringとしてなのであとでどこかでParse(Marshal?)するんでしょう。今は置いておきましょう。特にひねったこともなくフツーに定義されていましたね。一応
IP
型 の中身もみてみましょう。IP
src\net\ip.go
にありました。// An IP is a single IP address, a slice of bytes. // Functions in this package accept either 4-byte (IPv4) // or 16-byte (IPv6) slices as input. // // Note that in this documentation, referring to an // IP address as an IPv4 address or an IPv6 address // is a semantic property of the address, not just the // length of the byte slice: a 16-byte slice can still // be an IPv4 address. type IP []byteはぁ。
どうやら IP adrress は単純な構造体としてではなく、このパッケージ全体でセマンティックに定義されるもののようです。......とりあえず
IP
の役割が分かっているので良しとしましょう(´;ω;`).
(後で帰ってくるかも)次に引数を見てみます。
arguments : network, address
network, address stringですね。ただの文字列なんですが、入力例のとおり network ではプロトコルの種類(tcp4, tcp6, tcpのどれか), address ではURL + port番号を渡していました。
引数はただの string なので、振舞を知るには内部処理を見る必要がありますね。
内部処理
switch network { case "tcp", "tcp4", "tcp6": case "": // a hint wildcard for Go 1.0 undocumented behavior network = "tcp" default: return nil, UnknownNetworkError(network) } addrs, err := DefaultResolver.internetAddrList(context.Background(), network, address) if err != nil { return nil, err } return addrs.forResolve(network, address).(*TCPAddr), nilswitchブロック
まず
switch
ブロックで "tcp" のプロトコルの種類の指定以外をはじいてます。特別な処理はしていなく、network
に変な文字列が入っていたらエラーを返すだけです。Goにおけるリゾルバ : Resolver
addrs, err := DefaultResolver.internetAddrList(context.Background(), network, address)
DefaultResolver
のメソッドに引数を渡してaddrs
を取得しています。
デフォルトの「リゾルバ」。実はこのリゾルバは前回(第1回)インターネット層を勉強した際に記述をはしょったモノの1つでした。トランスポート層からデータを渡される際、インターネット層は DNS サーバ群による名前解決システムで URL から IP address を取得します。
例えばスマホから自宅のWifiで
qiita.com
にアクセスする時、自宅のルーターは ONU にリクエストのデータと、qiita.com
のヘッダーを付けて渡します。(トランスポート層からインターネット層へ)ONUは(ローカルな) DNS へ問い合わせを行います。このDNSは一定の範囲のIP addressとURLを紐づけて知っていて、自分が知っている範囲のURL, IP であればリクエストに従った処理を行います。
もしDNSにとって未知のURLに問い合わせがあった場合はどうなるのでしょう。
その場合よりグローバルなDNSへと問い合わせを行います。グローバルDNSはローカルDNS群の住所を知っていて、リクエストされていたIP address を管理しているDNSへと中継を行います。
後はこの繰り返しです。(各DNSはキャッシュを持っていたりするんですが大体こんな感じです)
そしてこの一連の流れを名前解決といい、DNSへ問い合わせて実アドレスを取得する機能を resolver と言います。Go はこれをサポートしています。
定義を見てみましょう。src\net\loolup.go よりvar DefaultResolver = &Resolver{} type Resolver struct { PreferGo bool StrictErrors bool Dial func(ctx context.Context, network, address string) (Conn, error) lookupGroup singleflight.Group }
context
に関しては......保留させてください。なんかGoプログラム自体の処理にかかわるインターフェースのようなんですが、ソース読んでもサッパリです。LookUpIPAddr
DefaultResolver
はinternetAddrList
インターフェースを持っていて、この中でメインの役割を果たすのがLookUpIPAddr
です。
コードを見てみましょう。場所はsrc\net\lookup.goです。// LookupIPAddr looks up host using the local resolver. // It returns a slice of that host's IPv4 and IPv6 addresses. func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) { ... }掘れば掘るほど出てきますね。これ以上やると終わらなくなるのでとりあえずここでストップします。(素人ではさらっと読めるコードじゃなかったです)
ローカルリゾルバを用いてホストを探す関数のようです。戻り値は([]IPAddr, error)
です。ResolveTCPAddrのまとめ
network, address
を引数にとって、TCPAddr
のポインタを返す関数だということが分かりました。
TCPAddr
はIP
とport
番号の組み合わせで表現されていて,IP
はbyte型で, パッケージ全体でセマンティックに定義されていました。いつか全部さらいたいですね。関数の中では
LookUpIPAddr
が呼び出されローカルリゾルバを再帰的にコールして(途中でエラーが無ければ)最後まで名前解決を行います(フルリゾルバ)。
これもLookUpAPAddr
の関数の中まではまだ追っていません。と、言うことで、
ResolveTCPAddr
は実際にネットワークに問い合わせを行いTCPアドレスを取得する関数だということが分かりましたね。
まあ関数名で大体予想がつくんですが。力尽きました
次回は
DialTCP
を見ていきます。
ResolveTCPAddr
が名前の通りリゾルバだったので、十中八九これはTCPコネクションを確立してソケットインターフェースを返す関数なんですが......一応中身を見ていきます。果たして第何回目でアプリのデプロイまでたどり着くのでしょうか・
- 投稿日:2019-02-09T01:25:05+09:00
久しぶりにGoでAPIサーバーを書いてみる
概要
- Go1.11からの新しい依存解決システムvgoはよい
- 昔はGoでMongoDBを使うときはmgoを使っていたが、Go1.10+ならMongoDBの提供するmongo-go-driverが使える
- freshを使えばGo製サーバーもライブリローディングしながら開発できる
- 自分と同じようにGoを敬遠していた人もそろそろ始めどきかも
はじめに
久しぶりにGoを使ってJSON APIサーバーを書く機会がありました。
Goは1.6とかの時代にちょっと触っていたのですが1、Python, Ruby, Nodeといったスクリプト系言語を主な使用言語としている自分にはだるいな…と思うことが多々ありましたし、ディレクトリの場所が規定されてしまうというのも嫌だなぁと感じていました。
というわけでしばらくGoとはサヨウナラをしていました。しかし最近バックエンドでGoを本格利用している会社の方が、最近Goもいろいろ変わっていてそろそろ再チャレンジするにはいい機会ですよ!と教えてもらいました。
特にGo1.11ではvgoという新しいバージョン管理システムが試験的に導入されており、これを使えばGOPATHはもういらないとのこと。スキルセットとして低レイヤーなことができてWASM時代にも備えられる言語に触れておきたい、でも言語仕様が難しいやつはちょっと…という自分にとってGoはまさしく求めているものだという自覚はありました。
ちょうどプロトタイプを一個作ってみるといった案件があったため、これは何かの機会だと思い再びGoにチャレンジしてみました。Docker
Goのバージョンを持っているPC全部でそろえるのは面倒なのでDockerを使うことに。
これだと別にvgo関係なくGOPATHどうこう気にする必要はなかったですね、、。Dockerfile
FROM golang:1.11 WORKDIR /go/src/app RUN go get github.com/pilu/fresh ENV GO111MODULE=on CMD ["fresh"]
ENV GO111MODULE=on
によってvgoが使えるようになりますが、その後go get
がうまく動かなくなるので先にgo get
しています。
vgoが有効だと、相対パス記法でのインポートなど古い仕様を切り捨てるようになっているようでその影響かなぁと。docker-compose.yaml
version: '3' services: app: build: context: ./app/docker ports: - 8081:8081 volumes: - ./app/src:/go/src/app command: fresh開発ディレクトリ(
./app/src
)をDockerfileで指定した作業ディレクトリにマウントします。これでfresh
を実行すると、自動的にmain.go
が立ち上がるようです。freshについては後述。まだビルドしてバイナリを出力するところまでは必要としていないので、まだその対応はしていません。
vgo
新しい依存管理システムです。importを書いておけば明示的にinstallとかしなくても必要があれば勝手にダウンロードしてくれる、Rubyのbundlerやnodeのnpmのようにバージョンロックできるというのが特徴です。
これを有効にすると相対パスを使ったインポートが使えなくなります。
main.goimport ( "./lib", ) ...
main.go
と同じディレクトリにgo.mod
を作成し、そこでプロジェクト名を定義し、<プロジェクト名>/<パス>
というようにインポートします。
go.modmodule appmain.goimport ( "app/lib" ) ...参考
go 1.11のmodules(vgo)が有効な環境で相対importが cannot find module for path でエラーになった話。mongo-go-driver
GoでMongoDBを使う際は、mgoというライブラリを使うのが定番だったようです。しかしこれはメンテナンス停止が宣言されており、フォークされたものを使っているケースが多いみたいですね。
しかし、MongoDBからmongo-go-driverというのがリリースされています。昨年12月にようやくアルファからベータになったばかりです。
Go1.10以上ということなので、これからGo+MongoDBという構成を導入するプロジェクトはこれを使ってみてもよさそうです。fresh
freshというツールを使えば、Railsで開発しているのと同じようにライブリローディングが使えます。同じようなことを実現するツールにはginというものもあります。(Webフレームワークのginとは別物)
どちらもあまり更新されていないようです…。が、あるのとないのとだと開発中の気持ちよさが全然違います。
Goをだるいと感じていた最大の原因がコードを変えたらプロセスを立ち上げ直す必要がある、だったのでそれがなくなると気分的にはRailsで開発しているのとそう変わりません。ちなみに、freshを使った環境構築はこちらを参考にしました。
Go v1.11 + Docker + fresh でホットリロード開発環境を作って愉快なGo言語生活私はginではなく昔触ったことのあるechoを使っていますが、全く同じ方法でOKです。
GOPATH云々はDockerを使えばもともと気にする必要がなかったわけですが
vgoはGoの依存解決の決定版になるのでしょうか。まだ軽く触っている段階ですが、だいぶ体験としてはよいですね。bundle install
とかnpm install
とか、よくよく考えたら明示的に呼び出す必然性ってないですね。
(昔depというのがあったように思うのですが、同じようにまた新しいのが出てきたりしないでほしいなー…と思います。)自分と同じように、昔触ったことがあるけどちょっとめんどくさくなって…という方は、そろそろ再チャレンジしてみてもよいかもしれません。
そのころGoで「世界で闘うプログラミング力を鍛える150問」を解いていたときの残骸です。https://github.com/k5trismegistus/cracking-the-coding-interview-150 ↩