はてなリモートインターンシップ2021のカリキュラムを発表します!

CTO の id:motemen です。先日募集開始した、はてなリモートインターンシップ2021のカリキュラムをお知らせします。はてなインターンのカリキュラムでは、参加する学生のみなさんに「モダンなウェブアプリケーション開発の学習」と「実際のプロダクト環境におけるサービス開発体験」の2つを持ち帰ってもらえるよう、毎年工夫をこらしています。

講義
全3週間のうち、前半1週間は講義フェーズです。以下のように、豊富かつ濃密な講義を予定しています。

Web API
RDBMS
フロ…

一緒に働く人に「次も呼んでもらえる」ような振る舞いを大切に | はてなで働く yigarashi にアンケート [#16]

はてなで働くエンジニアにアンケートシリーズ第16回は、はてなブックマークチームのWebアプリケーションエンジニアのid:yigarashiに話を聞きました。

社内のはてなidの分類では「jkondoスタイル」
突然新卒での応募をしたところ、通年採用のフローに
チームのデータ転送基盤の開発を主導+スクラムマスターとしてリーダーの立ち回り
適度にコンテキストスイッチをしながらいろいろな仕事をするのが好き
スクラム運営の強度が大きく向上+Embulkを用いたデー…

GraphQL IDE の “GraphiQL” をカスタマイズして、開発ツールとして活用する

こんにちは.マンガチームの id:mangano-ito です.最近は GraphQL API の開発を担当しており,GraphQL に関することを勉強したり実践したりしています.今回は開発ツールについてのお話です.

GraphiQL とは

graphql/graphiql: GraphiQL & the GraphQL LSP Reference Ecosystem for building browser & IDE tools.

GraphQL API の使いやすい GUI クライアントです.GUI クライアントなので GraphQL ではなく Graph i QL となっているのがポイントですね.

余談ですが「グラフィキューエル」と呼んでいたら「グラフィクル」だという声をいただき,「グラフィカル」にかけていると気づきました(ちなみにREADMEでは読み方が/ˈɡrafək(ə)l/と示されています).

ライブデモがあるので,どのようなものかはブラウザ上で確認できます.また Electron で実装されたアプリもあり,デスクトップ上でも使うことができます.

GitHub API での使用例

まず GitHub API の Explorer を見てみてください.これは GitHub の GraphQL API をブラウザ上でテストできるツールです.

GraphiQL の使用例: Github GraphQL Explorer

GitHub にサインインしていれば,自分のアカウントを使って GraphQL API をテストすることができます.実際のデータで確認できて便利です.

お気づきかと思いますが,ここに埋め込まれているクライアントは GraphiQL です.つまり GraphiQL を自分のウェブページにマウントすることができるということが,この例からわかります.

GraphiQL を導入してみよう

こうなるとこの例と同じように,開発しているサービスに特化した GraphiQL を便利に使えるようにしてみたいですね.どのようにすれば使うことができるでしょうか……

より詳しい情報はREADMEをあたっていきましょう.GraphiQL をモジュールとして使うための方法が書かれています.

graphiql/graphiql – GraphiQL (README.md)

ここにお試しで掲載されているケースでは,ただ CDN で配布されたスクリプトを読み込み,ReactDOM.renderで GraphiQL のコンポーネントを任意の要素にマウントすればそれで完了です!

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Simple GraphiQL Example</title>
    <link href="https://unpkg.com/graphiql/graphiql.min.css" rel="stylesheet" />
  </head>

  <body style="margin: 0;">
    <script crossorigin src="https://unpkg.com/react/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/graphiql/graphiql.min.js"></script>
    
    <div id="graphiql" style="height: 100vh;"></div>
    <script>
      const fetcher = GraphiQL.createFetcher({ url: '<GraphQL API エンドポイントの URL>' });
     
      ReactDOM.render(
        React.createElement(GraphiQL, { fetcher: fetcher }),
        document.getElementById('graphiql'),
      );
    </script>
  </body>
</html>

Usage: UMD Bundle over CDN より

React のコンポーネントをマウントする形になっているのはユニークですね.これだけで GraphiQL の機能がひと通りそろっていて,クエリもできるし,ドキュメンテーションもしっかりと表示できる状態になっています.

もちろんパッケージをnpm installして使うこともできます,本格的に組み込んで使える場合はこちらが都合よさそうですね.

import React from 'react';
import ReactDOM from 'react-dom';

import GraphiQL from 'graphiql';
import { createGraphiQLFetcher } from '@graphiql/toolkit';

const fetcher = createGraphiQLFetcher({
  url: '<GraphQL API エンドポイントの URL>',
});

ReactDOM.render(
  <GraphiQL fetcher={fetcher} />,
  document.body,
);

Usage: NPM module より

fetcherは,対象となる GraphQL API のエンドポイントを指定しリクエストするために必要なものです.

const fetcher = GraphiQL.createFetcher({
  url: '<GraphQL API エンドポイントの URL>'
});

以降のコードではこの基本形を基にして拡張してみます.

ツールバーをカスタマイズしてみよう

React のコンポーネントになっていることで,さらに面白い点としては,自分が作って任意のコンポーネントを GraphiQL 上に追加することができることです.

GraphiQLコンポーネントの属性により注目してみましょう.

<GraphiQL
  fetcher={fetcher}
  toolbar={toolbar} />

toolbarという属性があります.これはその名の通り,ツールバーの設定を記述するための属性です.ここにadditionalContentという属性を持ったオブジェクトを与えることで,任意の React コンポーネントを追加することができます.

const toolbar = {
  additionalContent: (
    <div>
      <GraphiQL.Button
        label="ボタンA"
        onClick={() => window.alert('こんにちは!')} />
      <GraphiQL.Button
        label="ボタンB"
        onClick={() => window.alert('さようなら!')} />
    </div>
  ),
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar} />
);

ReactDOM.render(
  <Editor />,
  document.getElementById('graphiql'),
);

例えばこのようなコードを書いてみます.GraphiQL.Buttonは GraphiQL のツールバーボタンのコンポーネントです.ツールバーに2つのボタンが追加されていることが確認できます.

ツールバーにボタンが追加された

コードから予想できるように,ボタンをクリックするとアラートが表示されます.

アラートが表示された

他に,ボタン以外のコンポーネントもあります.メニューは便利そうですね.

メニューの例
const toolbar = {
  additionalContent: (
    <GraphiQL.Menu label="メニュー">
      <GraphiQL.MenuItem
        label="おはよう"
        onSelect={() => alert('おはよう')} />
      <GraphiQL.MenuItem
        label="おやすみ"
        onSelect={() => alert('おやすみ')} />
    </GraphiQL.Menu>
  ),
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar} />
);

// ReactDOM.render 以下省略……

実はロゴ部分を変えることもできたりします.面白いですね.

ロゴ部分を変えられる
const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    toolbar={toolbar}>
    <GraphiQL.Logo>マイ GraphiQL</GraphiQL.Logo>
  </GraphiQL>
);

これで任意の機能をツールバーに登録することができることがわかりました.

ヘッダーやクエリをカスタマイズしてみよう

ところで,冒頭にご紹介した GitHub の例ではログイン状態を反映することができていました.

仮にログイン状態のためのAuthorizationヘッダーが API に必要だとしましょう.私たちが作る GraphiQL にその情報を追加するにはどうすればよいでしょうか?

GraphiQLコンポーネントの他の属性を見てみましょう.headersという属性があります.ここにヘッダーの内容を示す JSON 文字列を与えることで,任意の値を追加することができます.

const headers = {
  'Authorization': 'Bearer MY_TOKEN',
};

const Editor = () => (
  <GraphiQL
    fetcher={fetcher}
    headerEditorEnabled="true"
    headers={JSON.stringify(headers, null, 2)} />
);

開発者ツールでリクエストを確認すると,ヘッダーに指定したAuthorizationの値が設定されていることがわかります.

Authorization に値が設定された

またheaderEditorEnabledで,リクエストヘッダーがエディター上に表示され,編集もできるようになります.

ヘッダーエディタ表示

さらに,この値に state 変数を使うとリアクティブ変化するので……

const MyTokenButton = ({ setToken }) => (
  <GraphiQL.Button
    label='トークンをセット'
    onClick={() => setToken('NEW_TOKEN')} />
);

const Editor = () => {
  const [token, setToken] = React.useState('');
  const headers = {
    'Authorization': (!!token ?`Bearer ${token}`: undefined),
  };
  const toolbar = {
    additionalContent: <MyTokenButton setToken={setToken} />,
  };

  return (
    <GraphiQL
      fetcher={fetcher}
      toolbar={toolbar}
      headerEditorEnabled="true"
      headers={JSON.stringify(headers, null, 2)} />
  );
};

ボタンを押すとヘッダーにトークンが設定されるようになりました.

ヘッダーを動的に変更もできる

クエリも同様にquery属性を設定でき,onEditQueryで編集されたイベントを受け取ることもできます.

クエリも動的に変更できる
const FavoriteQueryButton = ({ setQuery }) => (
  <GraphiQL.Menu label="お気に入りクエリ">
    <GraphiQL.MenuItem
      label="その1"
      onSelect={() => setQuery("{ favorite1 }")} />
    <GraphiQL.MenuItem
      label="その2"
      onSelect={() => setQuery("{ favorite2 }")} />
  </GraphiQL.Menu>
);

const Editor = () => {
  const [query, setQuery] = React.useState('');
  const toolbar = {
    additionalContent: <FavoriteQueryButton setQuery={setQuery} />,
  };

  return (
    <GraphiQL
      fetcher={fetcher}
      toolbar={toolbar}
      query={query} />
  );
};

このようにして任意のヘッダーやクエリを設定することができました.よく使うアクションやサービス特有のテンプレートをツールバーボタンから設定できると便利になりそうですね.

実際に開発ではどう使っているか

最後に,自分が担当しているサービスでの実例を紹介します.

実際に開発で使っている GraphiQL の様子*1

GraphiQL に,開発用に以下の3つの機能を追加してみました.

  1. 自分のログイン情報を自動的に付加できるようにした
  2. デバッグ機能のためのプリセットや値をツールバーから使えるようにした
  3. 書いたクエリを共有できるようにして,開発者同士でシェアできるようにした

この中で3番目の「書いたクエリを共有」というのは,上図のように特定の URL でクエリが記入された GraphiQL を開くことができるものです.確認用のクエリを Pull Request で共有できて便利だったりします.

実装はごく単純で,URL の GET パラメーターにクエリを持たせてやりとりしているだけで,簡単に実現できます.

/**
 * ?query=... に今のクエリを設定する
 * @param {string} query 更新されたクエリ
 */
const onQueryUpdated = (query) => {
  const url = new URL(document.location);
  url.searchParams.set('query', query);
  window.history.replaceState(null, null, url);
};

/**
 * ?query=... のクエリを取得する
 * @returns {string|null} クエリ
 */
const getCurrentQuery = () => (
  new URL(document.location).searchParams.get('query')
);

// あとはこれを使うだけ
const Editor = () => {
  const [query, setQuery] = React.useState(getCurrentQuery());

  return (
    <GraphiQL
      fetcher={fetcher}
      query={query}
      onEditQuery={onQueryUpdated} />
  );
};

まだできていないこととして,あらかじめよく使うクエリを何らかの手段で参照できるようにして,スニペットとしてサッと使えるようにしてみたいと思っています.

GitHub API の Explorer では,次のようにExplorerボタンからクエリをお手軽に構築できる機能があり,こういう形でスニペットを挿入するイメージです.

GraphiQL 自体が React を使って柔軟に自作コンポーネントを追加できるので,生の要素や状態を管理することなく拡張できて便利です.思いがけず簡単に導入することができたのは面白い体験でした.

エンジニア採用 - 採用情報 - 株式会社はてな

*1:隠したり文言を少し変えたりしています

id:mangano-ito

マンガーノ・伊藤、伊藤聡介。アプリケーションエンジニア。マンガチーム所属。2019年12月入社時より現職。

Twitter: @mangano_ito
GitHub: mangano-ito
blog: マンガ〜ノ伊藤ノ〜ト

GitHub ActionsでGoのソースコードをクロスコンパイルするときに、ビルドが失敗する理由とその対策

Mackerelチームでアプリケーションエンジニアをやっているid:lufiabbです。

Mackerelでは、ホストのメトリックを送信するためのエージェントや各種プラグインなどをOSSとして公開しています。現在の公式サポートはヘルプにある対応環境の通りですが、サポート外ではあるもののFreeBSDや32bit Windowsなどにもプログラムを提供している場合があります。

こういったOSSの一部、特にGo言語で書かれたプログラムのCIをGitHub Actionsへ移行した際に、32bitバイナリの生成についていくつか調べたことがありますが、あまりインターネットで見かけない情報だったので、今回は一般的な情報として共有してみようと思います。

この記事では、ディスクの残り容量を調べるプログラムの64bit版と32bit版をLinux用とWindows用にビルドする方法を例に、以下の場合にそれぞれ何が必要になるのかを見ていきます。

具体的な方が伝わりやすいと思うのでGoのコードも書いていますが、この記事の主題はビルド方法なので、サンプルコードの内容は雰囲気で読んでいただいても大丈夫です。なお、説明で使ったコードは以下のリポジトリで公開しています。段落ごとにコミットしているので、興味があれば眺めてみてください。

lufia / cross-compile-example

ビルド制約を含む場合

ビルド制約(Build constraint)は、ファイル名に_linux_amd64と入れたり、ファイルの先頭に// +build linux,amd64と書いて、ビルド対象となるファイルを切り替えるものです。

// main.go

func main() {
    root := Fsroot()
    fmt.Println(root)
}

上記のようにファイルシステムのルートディレクトリを取得する場合、UnixやPlan 9では次のように単純に/を返せばいいでしょう。

// +build linux

// fs_linux.go

package main

func Fsroot() string {
    return "/"
}

Windowsの場合は、ドライブレターを含むのでビルド制約で切り替えます。ドライブレターは変えられるので本当は環境変数SYSTEMDRIVEなどを使った方がいいのですが、以下はサンプルなのでCドライブで固定です。

// +build windows

// fs_windows.go

package main

func Fsroot() string {
    return `C:\`
}

この場合、GitHub Actionsのワークフローは素朴にmatrixを使うだけで済みます。

jobs:
  test:
    strategy:
      matrix:
        GOOS: ['linux', 'windows']
        GOARCH: ['amd64', '386']
        include:
        - GOOS: windows
          X: .exe
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v2
    - uses: actions/cache@v1
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
    - uses: actions/setup-go@v2
      with:
        go-version: 1.16.x
    - run: |
        mkdir -p dist
        go build -o dist/hdb-$GOOS-$GOARCH$X
      env:
        GOOS: ${{ matrix.GOOS }}
        X: ${{ matrix.X }}
        GOARCH: ${{ matrix.GOARCH }}
      shell: bash
    - uses: actions/upload-artifact@v2
      with:
        name: dist
        path: dist/hdb-*

このワークフローが正常に終われば、Actionsにアップロードされたzipファイルに4つの実行ファイルが入っていることが確認できます。

なお、Go 1.4以前は$GOROOT/src/make.{bash,bat,rc}でクロスコンパイルのターゲットを用意しておく必要がありましたが、現在は不要です。

システムコールを使う場合

次に、システムコールやWin32 APIを使う場合です。

例として、ファイルパスを与えると、そのファイルが含まれるディスクの空き容量を返す関数を作ります。使う側はこんな雰囲気です。

// main.go
func main() {
    log.SetFlags(0)
    size, err := DiskAvail(Fsroot())
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println("avail size(GB)", size/1024/1024/1024)
}

ディスク容量を調べるため、Linuxの場合はStatfsシステムコールを使います。同じものはsyscallパッケージにもありますが、これはGoのサポートに必要なものしか含まないので、一般的な用途ならgolang.org/x/sysを使いましょう。

// fs_linux.go
import "golang.org/x/sys/unix"

func DiskAvail(path string) (uint64, error) {
    var s unix.Statfs_t
    if err := unix.Statfs(path, &s); err != nil {
        return 0, err
    }
    return s.Bavail * uint64(s.Bsize), nil
}

Windowsの場合は、Win32 APIを使います。以下の例では、uintptrにキャストするためunsafeを使っていますが、この用途であれば安全です。

// fs_windows.go
import (
    "unsafe"

    "golang.org/x/sys/windows"
)

var (
    modkernel32 = windows.MustLoadDLL("kernel32.dll")

    GetDiskFreeSpaceEx = modkernel32.MustFindProc("GetDiskFreeSpaceExW")
)

func DiskAvail(path string) (uint64, error) {
    var size int64
    upath := windows.StringToUTF16Ptr(path)
    if r, _, err := GetDiskFreeSpaceEx.Call(uintptr(unsafe.Pointer(upath)), 0, 0, uintptr(unsafe.Pointer(&size))); r == 0 {
        return 0, err
    }
    return uint64(size), nil
}

この場合も、前述したビルド制約の場合と同じワークフローで全てのバイナリをビルドできます。

Windows版のコードではDLLをロードしているのでcgoが必要かと思いましたが、どうやらそうでもないようです。

cgoを使う場合

cgoを使うと、GoからCのライブラリをリンクできます。上記の例ではunix.Statfsシステムコールを使いましたが、Cライブラリのstatvfsを使いたい場合があるかもしれません1。もっと実用的なものでは、グラフィックスライブラリを使う場合でしょうか。

cgoでは、import "C"した上で、次のようにコメントの中にCのコードを書きます。

/*
#include <stdlib.h>
*/
import "C"

これは#includeするだけのCコードですが、これをfs_linux.gofs_windows.goに入れてビルドしてみましょう。

undefinedエラーでビルドに失敗する

この場合、前述したワークフローのままでは、Linuxの64bit版を除いて以下のエラーで失敗します。

./main.go:11:15: undefined: DiskAvail
./main.go:11:25: undefined: Fsroot

この動作は、import "C"を含む場合の暗黙的なビルド制約と、クロスコンパイル時の環境変数$CGO_ENABLEDによるものです。まず、import "C"を含むファイルにはcgoビルド制約がセットされるため、CGO_ENABLED1の場合にのみビルド対象として含まれます。

ところが、$CGO_ENABLEDはビルドするホスト($GOHOSTOS$GOHOSTARCH)とターゲット($GOOS$GOARCH)が同じならば1がデフォルトですが、クロスコンパイルする場合にはデフォルトが0になります。

つまり、cgoの記述を含むファイルは、クロスコンパイルする場合にデフォルトでビルド対象から外されます。

上記のワークフローはubuntu-20.04でビルドしているので、32bit版のLinuxバイナリをビルドする場合にfs_linux.goはビルド対象に含まれません。同じように、Windowsでは32bitと64bitのどちらも、fs_windows.goが含まれなくなります。このため、一緒に定義している関数も定義されなくなって、上記のエラーになります。

次のように、ワークフローでCGO_ENABLED1にセットしましょう。

    - run: |
        mkdir -p dist
        go build -o dist/hdb-$GOOS-$GOARCH$X
      env:
        GOOS: ${{ matrix.os.GOOS }}
        X: ${{ matrix.os.X }}
        GOARCH: ${{ matrix.GOARCH }}
        CGO_ENABLED: 1
      shell: bash

unrecognized command line optionのエラー

次は、Windows版をビルドする場合に下記のエラーが発生すると思います。

gcc: error: unrecognized command line option ‘-mthreads’; did you mean ‘-pthread’?

Windowsでcgoを使う場合、Cコンパイラに-mthreadsオプションを渡すようになっていますが、このオプションはMinGWのgccで用意されているもので、Linuxにはありません。

-mthreadsは、gccのmanページによると次の記述があり、他にもWindowsのマルチスレッドライブラリを参照するように切り替えたりなどするようです。

Support thread-safe exception handling on MinGW. Programs that rely on thread-safe exception handling must compile and link all code with the -mthreads option.

Linuxにgcc-mingw-w64などをインストールしてMinGWのバイナリを作ることもできますが、GitHub ActionsにはWindowsホストも用意されていますし、Windowsホストの場合はWiXでインストーラを作ったりなどもできます。

このためMackerelのOSSではワークフローのジョブを分けて、Windowsホストを使うようにしました。こちらはLinuxの場合のジョブです(主な変更だけ)。

jobs:
  build-linux:
    runs-on: ubuntu-20.04
    steps:
    - run: |
        mkdir -p dist
        go build -o dist/hdb-linux-$GOARCH
      env:
        GOARCH: ${{ matrix.GOARCH }}
        CGO_ENABLED: 1
      shell: bash

こちらがWindowsの場合です(主な変更だけ)。

jobs:
  build-windows:
    runs-on: windows-2019
    steps:
    - run: |
        mkdir -p dist
        go build -o dist/hdb-windows-$GOARCH.exe
      env:
        GOARCH: ${{ matrix.GOARCH }}
        CGO_ENABLED: 1
      shell: bash

これで、LinuxとWindowsのどちらでも、64bit版はビルドが通るようになります。しかし、32bitの場合は両OSともまだエラーになります。

Linuxの32bit版バイナリを生成する

Linuxの32bit版バイナリを生成するときのエラーは、以下のような内容です。

In file included from _cgo_export.c:3:
/usr/include/stdlib.h:25:10: fatal error: bits/libc-header-start.h: No such file or directory
   25 | #include <bits/libc-header-start.h>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

このエラーでは、文字通りファイルがないと言っています。Linuxにはmultilibといって、異なるアーキテクチャのバイナリをビルドまたは実行する仕組みがあります。Ubuntuの場合、bits/libc-header-start.hgcc-multilibに含まれているので、これをインストールすれば解決します。

- run: |
  sudo apt-get install gcc-multilib g++-multilib

以上で、cgoを使う場合でもLinux版は32bitと64bitに対応できるようになります。

Windowsの32bit版バイナリを生成する

Windowsの32bit版バイナリを生成するときのエラーは、以下のような内容です。

C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingwthrd.a when searching for -lmingwthrd
...このようなエラーがいっぱい...
collect2.exe: error: ld returned 1 exit status

../が含まれていて長いので、要約すると次のようになります。

/mingw64/x86_64-w64-mingw32/bin/ld.exe: skipping incompatible /mingw64/x86_64-w64-mingw32/lib/libmingwthrd.a when searching for -lmingwthrd

mingwthrdを検索してlibmingwthrd.aが見つかったけど、互換性がないのでスキップすると言っていますね。これは32bit版のバイナリをビルドするときのCコンパイラを、32bitターゲット用のi686-w64-mingw32-gccに変更するとよいです。ちなみに64bitターゲットのgccは、x86_64-w64-mingw32-gccです。

cgoは、特定環境用の環境変数$CC_FOR${GOOS}${GOARCH}があればCソースコードをコンパイルするときに使いますし、$CC_FOR_TARGETがあれば$CCに優先して使います2。この場合は次のように指定しましょう。

- run: |
    mkdir -p dist
    go build -o dist/hdb-windows-$GOARCH.exe
  env:
    GOARCH: ${{ matrix.GOARCH }}
    CGO_ENABLED: 1
    CC_FOR_windows_386: i686-w64-mingw32-gcc
  shell: bash

基本的にはこれでよいはずですが、エラーメッセージはまだ変わりません。対応を次で見ていきます。

ところで、x86_64-w64-mingw32-gccというコマンド名には32や64という数字がいくつか入っていますが、これは何でしょうか。ターゲットアーキテクチャを表す文字は、先頭のx86_64またはi686の部分だけで、他はターゲットに関係ないようです。もともとmingw32という名前で、64bit Windowsに対応したフォークがmingw-w64なのでその辺りが由来かなと思いますが、他にもパス部分にmingw32やmingw64表記などもあったりしていて分かりづらいですね。

Mingw-w64 – GCC for Windows 64 & 32 bits [mingw-w64]

mingw-w64-i686-gccパッケージのインストール

どうやらGitHub ActionsのWindows環境には、i686-w64-mingw32-gccは入っていないようです。MinGWに含まれるpacmanを使えばパッケージの追加はできるみたいですが、MSYS2公式のmsys2/setup-msys2アクションを使った方が簡単そうだったので、これを使うことにします。

MSYS2

setup-msys2で追加したMinGWはテンポラリディレクト置かれていて、gccにPATHは通っていないので、シェルの指定をshell: msys2 {0}とする必要があります。

結果、ワークフローは以下のように変わります(変更のある部分だけを抜粋)。

strategy:
  matrix:
    GOARCH: ['amd64', '386']
    include:
    - GOARCH: amd64
      MSYSTEM: MINGW64
    - GOARCH: '386'
      MSYSTEM: MINGW32
- uses: msys2/setup-msys2@v2
  with:
    msystem: ${{ matrix.MSYSTEM }}
    path-type: inherit
    install: mingw-w64-i686-gcc
- run: |
    mkdir -p dist
    go build -o dist/hdb-windows-$GOARCH.exe
  env:
    GOARCH: ${{ matrix.GOARCH }}
    CGO_ENABLED: 1
    CC_FOR_windows_386: i686-w64-mingw32-gcc
  shell: msys2 {0}

注意するべき点としては、msys2シェルを実行すると、デフォルトではWindowsでセットしているPATHを含みません。

Goの環境はactions/setup-goで入れていて、これはMSYS2のデフォルトではPATHに含まれないので、path-type: inheritとしてWindows側のPATHもmsys2シェルへ引き継ぐようにしています。また、mingw-w64-i686-gcc/mingw32以下にインストールされるので、MSYSTEM: MINGW32/mingw32/binをPATHへ追加しています。

これで、cgoを使う場合でもクロスコンパイルできるようになりました。とはいえcgoを使うと、それだけで環境を用意するのも大変だし、ビルドも遅くなるので、どうしても必要な場合を除いてcgoは使わない方が良いと思います。


  1. この程度ならGoで書いた方がいいとは思いますが。

  2. C++の場合はCXX_FOR_TARGETのように、環境変数名のCC部分がCXXと変わります。

id:lufiabb

門多恭平。2018年12月の入社時より、Mackerelチームのアプリケーションエンジニア。OSのPlan 9を愛用し、Go言語では並列性を利用した大規模プロダクトを手掛けた経歴もある。

Twitter: @plan9user
GitHub: lufia
blog: Plan 9とGo言語のブログ

新しい技術を追いかけるのが好き | はてなで働く nabe1216 にアンケート [#15]

はてなで働くエンジニアにアンケートシリーズ第15回は、マンガサービスのiOS/Androidアプリエンジニア、id:nabe1216に話を聞きました。

「関モバ」などのイベントではてなの話を聞いた
Androidアプリ開発をメインに、iOSアプリ開発にも関わる
頭を仕事モードに切り替えてからタスクに着手
GraphQL導入で「一歩外の世界へ」
人見知りでなかなか懇親会まで参加できない
ユーザーが直接触れるアプリのデザインや使い心地を大切にする

4月27日(火)に Hatena Engineer Seminar #16 Mackerel チームの技術と働き方編 をオンラインで開催します

こんにちは。はてなチーフエンジニア 兼 Mackerel プロダクトマネージャーの id:wtatsuru です。4月27日(火)に Hatena Engineer Seminar #16 をオンラインで開催します!今回はMackerelのサービスを支えるアプリケーションエンジニアとCRE(Customer Reliability Engineer)2名によるトークをご用意いたしました。Mackerelの仕組みや開発の裏側から、ユーザーを支えるMackerelならではCREの取り組みなど、はてなのエ…

はてなリモートインターンシップ2021募集開始!今年はリモートでプロダクト開発も!

CTO の id:motemen です。このたび、はてなリモートインターンシップ2021の募集を開始しました。2021/8/16(月)~2021/9/3(金)に開催する長期インターンシップとなります。「はてなリモートインターンシップ2021」は、全日程をオンラインで開催する3週間、15日間のインターンシップです。 Webサービス開発の技術を学ぶ前半パートと、開発チームに配属されてサービス開発を実践する後半パートで構成されます。

前半日程は、Webサービスの開発・運用について、講義と課題による集中…

「データドリブンな意思決定」が浸透しつつある | はてなで働く polamjag にアンケート [#14]

はてなで働くエンジニアにアンケートシリーズ第14回は、はてなブログのWebアプリケーションエンジニア、id:polamjagに話を聞きました。

はてなidの由来は秘密
2016年のインターンからアルバイトを経て入社
チーム全体でデータと触れ合っていけるようにしたい
テックリードには広い視野が必要、だけど……
チーム内で「データドリブンな意思決定」の方向にうまく動けた
「データドリブンな意思決定」の成果を出すにはまだ道半ば
「毛色が違うこと」…

エンジニア新人研修の一環で株式会社はてな 社内 ISUCON を開催しました

シニアエンジニアの id:astj です。ISUCON は予選上位までいけるものの本選には届かないことが多いです。先日、昨春入社したアプリケーションエンジニアを対象としたエンジニア新人研修*1の一環で株式会社はてな 社内 ISUCON を id:dekokun id:hokkai7go と3人で企画・開催しました。このエントリではその様子について紹介しようと思います。※「ISUCON」は、LINE株式会社の商標または登録商標です。
ISUCON についての詳細は ISUCON公式ブログでご確認くだ…

はてなリモートインターンシップ2021を開催します!

追記ここまで

はてなリモートインターンシップ2021開催決定

こんにちは、CTO の id:motemen です。

はてなは今年も、学生を対象としたエンジニア向けの夏のインターンシップ、「はてなリモートインターンシップ2021」を開催します。

現時点での新型コロナウイルス感染拡大状況などを考慮し、リモートでの短期インターンの実施を企画しています! 今年のインターンでは、8月後半、3週間程度のプログラムとなる予定です。

リモート開催に最適化したカリキュラムを用意します。
前半はWebアプリケーション開発のオンライン講義を実施後、数日かけて課題に取り組んでもらい、はてなのエンジニアスタッフによるコードレビューや面談でフィードバックを行います。
後半はチーム開発体験です。「フレキシブルワークスタイル制度」という在宅勤務・出社を全スタッフが選択可能な制度を採用しています。これに基づいたリモートワークでのチーム開発に参加していただき、実際のプロダクションのコードを使った開発を体験していただこうと思っています。

開催時期、応募方法などの詳細については、4月中旬をめどにこのブログでお知らせします。インターンシップへの参加にご興味のある方は、以下のフォームからメールアドレスを登録いただくと、詳細情報を公開した際にメールでお知らせします。ぜひご登録下さい。

「はてなリモートインターンシップ2021申し込み」はこちら

(追記)募集サイトを公開いたしましたのでフォームはクローズいたしました。上記リンクより募集サイトへ遷移ください。

インターン最新情報はTwitterでも発信していきます

インターンの情報は、Twitter アカウント @hatenatech でも積極的に発信していきます。興味のある方は是非フォローしてください!

エンジニア向けはてなインターンとは

2008年より毎年開催され、今年で14年目となるインターンシッププログラムです。はてなスタッフが講師・メンターとなり、学生のみなさんにWebサービス開発者としての技術を身につけていただくための、オリジナルのプログラムを提供しつづけてきました。若き皆さんのための強烈な体験を提供し、業界に優秀なエンジニアを送り出す試みです。

過去のインターンの様子は、下記のレポート記事をご覧ください。