macOS Sierra上でGAE/Go SDKが動くようになった

https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_darwin_amd64-1.9.46.zip

gcloudツールからのコンポーネント管理では現時点アップデートされていませんでしたが、GAE/Go単体SDKはGo1.6.3ベースにアップデートされていました。

再度GAEで動くように環境変数を設定しなおして、GAEお休みのために一時避難的に書いてたmain.main()がAppEngine SDKが補うものとぶつかってコンパイルエラーになったのをコメントアウトしたら、それまでのようにいきなりクラッシュするようなことなく、元気に動いてます。

ディスク容量を圧迫しているので、ちょっと様子みてからにしますが仮想El Capitan環境は削除しようと思う。

reduxとreact-routerの間でURLとstateの同期を行う

reactのルーティング機能?というかテンプレートシステムというか?にreact-routerがあります。リクエストパスに応じて画面構成要素を組み立てるのでreact-routerを使ってアプリケーションを作ると当然URLと画面は同期することができます。URLを直打ちしてもコードでURL遷移させても、適切な画面に切り替わるように作れるのです。

reduxはその際にどうかというと、URLや画面構成といったものについて素で働きかけるものではないので、URLが変化してもreduxのステートマシンにはその情報は反映されません。そこにはもう一つ上乗せするものが必要です。いくつか同じ守備領域のライブラリがあるようですが、わたしはここのところしばらく、中でも主流感のあるreact-router-reduxを用いています。

GitHub - reactjs/react-router-redux: Ruthlessly simple bindings to keep react-router and redux in sync

render、Route、Store

react-router-reduxはURLをreact-routerのURL管理の仕組みHistoryをフックすることでURL変化を取得します。

import React from 'react';
import { render } from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory, IndexRedirect } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';

import { BookList, BookReader, Login, NotFound, Navigation, Theme } from './containers';
import app from './reducers/AppReducer';
import routing from './reducers/RoutingReducer';

// routingという名前はreact-router-reduxの仕様として固定
const store = createStore(combineReducers({ app, routing }));

render(
    <Provider store={store}>
        <Router history={syncHistoryWithStore(browserHistory, store)} >
            <Route path="/" component={Theme}>
                <IndexRedirect to="book" />
                <Route path="book" component={Navigation}>
                    <IndexRedirect to="list" />
                    <Route path="list" component={BookList} />
                    <Route path=":title" component={BookReader} />
                </Route>
                <Route path="*" component={NotFound} />
            </Route>
        </Router>
    </Provider>,
    window.document.getElementById('application')
);

syncHistoryWithStore(browserHistory, store) でHistoryとreduxのstoreを結びつけていますが、このstoreの中で同期のコードを書くことになります。その前にまず、画面遷移をするためのAction Creatorは以下のようにしました。

// /actions/index.js
import { createActions } from 'redux-actions';

export default createActions(
    {
        MOVE_TO: (title, page = 0) => {
            const finalPage = page < 0 ? 0 : page;
            return { title, page: finalPage };
        },
    },
);

この’MOVE_TO'/moveToは文字列のtitleと数字のpageを設定するものです。サンプルとしてページがマイナスにならないようにだけチェックしてます。redux-actionsの書き方として、昨日の記事のように名前だけでAction Creatorを用意する方法の他、このようにaction.payloadに搭載する内容をコードで調整することも可能です。

アプリケーションのReducer

// /reducers/AppReducer.js
import { handleActions } from 'redux-actions';
import { LOCATION_CHANGE } from 'react-router-redux';

 // DANGER: It's an internal API of react-router!
import { matchPattern } from 'react-router/lib/PatternUtils';

import actions from '../actions';

// URL -> History -> Locationの情報からtitleとpageを抜き出す
function decodeTitleAndPage({ pathname, query }) {
    const m = matchPattern('/book/:title', pathname);
    if (!m) {
        return { title: null, page: 0 };
    }
    const { paramNames, paramValues } = m;
    let title = null;
    for (let i = 0; i < paramNames.length; i += 1) {
        if (paramNames[i] === 'title') {
            title = decodeURI(paramValues[i]);
            break;
        }
    }
    const page = (query.page && Number(query.page)) || 0;
    // titleとpageを返している!
    return { title, page };
}

export default handleActions({
    // こちらはアプリケーションの基本的なReducer。なにはなくともこれは書くことでしょう
    [actions.moveTo]: (state, { payload: { title, page } }) => ({
        ...state,
        title,
        page,
    }),
    // こちらがreact-router-reduxの作法として実装すべき内容。LOCATION_CHANGEをappとして監視する。
    [LOCATION_CHANGE]: (state, { payload }) => ({
        ...state,
        ...decodeTitleAndPage(payload),
    }),
}, { /* default state here */ });

まずアプリケーション側のReducerですが、こちらはreact-router-reduxの定義する"LOCATION_CHANGE" Actionを処理するものを作ります。このActionはpayloadにURL由来のLocationを積んでるので、そこからリクエストされているパスやクエリの内容を取り出します。取り出しているのは、上記のdecodeTitleAndPage()と書いてあるところです。

こちらでは手抜きでreact-routerの内部APIを使ってます。パスを分解して当たり判定するコードを書くのが大変なのでreact-routerで用いられているmatchPattern関数を引っ張ってきました。この関数を用いてreact-routerの仕様で書かれたルートパラメータであるtitleを取り出すことができました。取り出した値はアプリケーションのstateに保持されます。このことによってURLからRedux Storeへの方向の同期ができるようになりました。

ルーティングのためのReducer

// reducers/RoutingReducer.js
import { handleActions } from 'redux-actions';
import { LOCATION_CHANGE } from 'react-router-redux';
import actions from '../actions';

// titleとpageからLocationオブジェクトを作る
function encodeTitleAndPage({ locationBeforeTransitions }, { title, page }) {
    const pathname = `/book/${encodeURI(title)}`;
    return {
        ...locationBeforeTransitions,
        pathname,
        query: { page: page.toString() },
        search: `?page=${page}`,
        action: 'PUSH',
    };
}

export default handleActions({
    // こちらがreact-router-reduxの作法として実装すべき内容。moveToをroutingとして監視する。
    [actions.moveTo]: (state, { payload }) => ({
        ...state,
        locationBeforeTransitions: encodeTitleAndPage(state, payload),
    }),
    // こちらはreact-router-reduxのお約束。
    [LOCATION_CHANGE]: (state, { payload }) => ({
        ...state,
        locationBeforeTransitions: payload,
    }),
}, { locationBeforeTransitions: null });

もうひとつ、逆にRedux StoreからURLへの方向の同期を行うためのものです。このReducerは”routing”の名前でStoreに登録されるべきもので、冒頭にでてきてStoreとHistoryを結びつけたsyncHistoryWithStoreから参照されるものになります。stateのrouting.locationBeforeTransitionsが変化すると、HistoryのAPIを呼び出してURL遷移します。URLの変化によってsyncHistoryWithStoreが投げるLOCATION_CHANGEに対応するものに加えて、アプリケーションコードからtitleとpageを指定することで期待する画面遷移を、moveToのReducerで書きます。これは例でencodeTitleAndPageとした関数で書いていて、moveToのpayloadに積まれているtitleとpageをLocationオブジェクトに反映しています。

  • Location
    • pathname: 遷移先パス
    • query: クエリパラメータ
    • search: 遷移先URLに追加される、クエリパラメータを含んだ文字列。
    • action: PUSHいれておけばOK。ちょっと他のケースが思いつかない。

冗長に思うけどqueryと両方設定してあげないとダメな作りだった。注意されるべし。

combineReducersで分割管理されたReducer群の挙動

この一連の作りを掘った際に深く理解できたのは、ReduxのcombineReducersでまとめたReducerの動きであり、一度Actionが投げつけられれば、分割されたすべてのReducerにActionがバインドされるってことです。アプリケーションとルーティングのための二つのReducerとしてappとroutingが同じstateの中に名前空間をつくってますが、appにもroutingにもmoveToが同時に発火し、LOCATION_CHANGEも同時に発火する。だからこの例のような作りができる。

感想

これで、URLを直で入力されてもステートマシンに適切に反映され、アプリケーションでreduxのactionをバインドするだけでURL遷移させることができるようになりました。これは結果としてブラウザの戻るボタンや進むボタンの対応も完全におこなわれたということであり、それがreact-router-reduxのサイトの説明にある謎の言葉「time travel」の説明となります。

You want to do time travel with your application state, but React Router doesn't navigate between pages when you replay actions. It controls an important part of application state: the URL.

(https://github.com/reactjs/react-router-redux より引用)

初見では何言ってんのかわかりませんでしたが、なるほど納得。この文はステートマシン->URLのことしか言ってないように読めますけど、説明したように逆も大事。react-router-reduxの標準のサンプルにはURL -> ステートマシンのことが書かれてなかったので見はじめてしばらくはダメダメなライブラリと思ってました。その後に全くの別件でcombineReducersのつかいこなし例を見たときにはじめてreact-router-reduxの思想に思い至り、ビビビと痺れた。routing Reducerは標準で用意されているものではなく、自分で書かないと真価が発揮されないものなのだと。つまりおしいかな、ライブラリで用意されたドキュメントとサンプルは説明が足りない。

備忘録

routing.locationBeforeTransitions がお約束の言葉。

reduxはredux-actionsギプスをつけて養成

reactアプリケーション開発においてコンポーネントに引数として渡されるpropsと、スコープの大きな実行時変数としてのcontextに加えて、もっぱらコンポーネント内部の状態管理としてのstateをあれこれ操作するのがデータフローの基本だったのですが、ここ一年ぐらいで様子が一気に変わってしまいました。reactの普及と対象とするアプリケーション規模が大きくなることによる複雑性の課題に応えてFluxが提唱され、その実装バリエーションで淘汰を経たreduxの登場です。それまで標準であったコンポーネントstate管理によって多数散在してしまった複数ステートマシンの連携を否定して、アプリケーションレベルで一つの大きなステートマシンをつくってpropsを通じてのみコンポーネントツリーに反映させるという。たくさんあるものをn:nに対応させていくってのは複雑を、常に抽象から具体へ1:nになる流れに修正すると一気にシンプルになり、わかりやすくなりました。

redux-actions

そんなreduxはこれまたライブラリとしてみると極薄で、実装の際に自由度がとても高い。これはテストや設計などでメリットとなる一方で、どう書いてもいいってのは、そもそもに開発者に悩みを与え、構成としては将来に技術的負債を生じさせてしまうリスクを持ってます。

GitHub - acdlite/redux-actions: Flux Standard Action utilities for Redux.

redux-actionsはその説明の冒頭にあるように、FSA(https://github.com/acdlite/flux-standard-action, =Flux Standard Action)として引いたガイドラインのライブラリです。reduxでマジにどうやったっていいactionCreatorの仕様と実装に堅めのガイドラインをFSAが引き、redux-actionsが実装の手助けをしてくれる。。。って今きがついたのですが、作者いっしょじゃん。なるほどねー。FSAの冒頭にあるように「A human-friendly standard for Flux action objects. 」なのはそのとおり。すでにreduxエコシステム界もこのFSAをスタイル標準として反応している様子なので、逆らわずに巻かれていいとおもう。

// /actions/index.js
import { createActions } from 'redux-actions';

export default createActions(
    'REQUEST_LIST',    // -> actionCreatorの識別子はrequestListと自動的に名付けられる
    'RECEIVE_LIST',      // -> action.typeは'RECEIVE_LIST'
);

ActionCreatorはこれでOK。それぞれの名前に応じて、引数をそのままaction.payloadに積むactionCreatorが用意されます。ドキュメントにも書かれてるけど、createActionsではなくて本当はcreateActionCreatorsとでもするべきなんでしょう。でもcreateActionsです。プレーンオブジェクトに用意したactionCreatorを詰め込んだものを返してくれますので、そのままexport defaultしました。

対するreducerもまっさらにswitch文を書く代わりにredux-actionsの支援を用いて書けます。

// /reducers/AppReducer.js
import { handleActions } from 'redux-actions';
import actions from '../actions';

export default handleActions({
    [actions.requestList]: state => ({
        ...state,
        fetch: true,
    }),
    [actions.receiveList]: {
        // 正常時
        next: (state, { payload }) => ({
            ...state,
            list: payload,
            errorMessage: null,
            fetch: false,
        }),
        // 例外時
        throw: (state, { payload }) => ({
            ...state,
            list: null,
            errorMessage: payload.message,
            fetch: false,
        }),
    },
}, { /* default state here */ });

こちらは間違いなくhandleActionsで名も実もいいですね。import actions from '../actions';から [actions.requestList]:としてるところは、createActionsで用意されたActionCreatorがtoString()でAction Typeを返してくれる実装をしてるからです。気がきいてます。handleActionsの馬鹿でかくなってしまう第一引数に続いて、第二引数に空オブジェクト(default state hereのところ)をおいてますが、これはstateの初期値設定です。このオブジェクトを省略するとヌルポが起きるので注意。

外のAPIを叩いてもどってくる作りのときには、FSAがActionCreatorに渡された引数を常にpayloadに積むことと、例外通知にはpayloadにエラーを突っ込んだ上で、action.errorをtrueにするということが効いてきます。reducerの実装が関数のときは正常時も例外時も関係なくそのまま実行するし、nextとthrowという名前のメソッドを持つオブジェクトを設定すれば、上の例のreceiveListがそうですが、正常時はnextを実行し例外時にはthrowを実行しわけるreducerを作れる。小さな規約でシンプルかつ堅い。

reduxでmiddleware書くなら、FSA前提でつくっちゃったほうが悩みが少ないです。たとえば以下のCookieと値をコピーするMiddlewareでは、action.metaというもう一つの規約をもちいて処理のきっかけをもたせた上で、値は常にaction.payloadに乗ってるという前提あっての実装です。そうじゃないとどうくるかわからないactionに対応するためにもっと複雑な作りになっちゃう。

import Cookies from 'browser-cookies';

export default function (options = { expires: 1 }) {
    return (/* store */) => next => (action) => {
        const name = action.meta && action.meta.cookie;
        if (name && !action.error) {
            if (action.payload) {
                Cookies.set(name, JSON.stringify(action.payload), options);
            } else {
                const value = Cookies.get(name);
                if (value) {
                    try {
                        next({ ...action, payload: JSON.parse(value) });
                    } catch (e) {
                        next({ ...action, payload: e, error: true });
                    }
                    return;
                }
            }
        }
        next(action);
    };
}

babel

ところで、ESの様々な新文法つかうとreduxって気持ちいいんですが、トランスパイルするためにはbabelで設定が必要。

  "babel": {
    "plugins": [
      "transform-decorators-legacy"
    ],
    "presets": [
      "es2015",
      "react",
      "stage-1"
    ]
  },

私はpackage.jsonでこのようなbabelの設定しています。presetsのstage-1がないと、reducerでstateを「...」を用いて展開するのができない。これができるとできないではコードに大きな違いが生じます。transform-decorators-legacyは今回とは別で、redux-formの私の書き癖のために登録しています。

ちなみにESLintは以下のように。WebStormの標準の体裁にあわせたほかは、airbnbです。去年書いてた時にはairbnbはどうにも馴染まないルールが多くて無理ゲーだったんだけど、いま書いてみると特に違和感ない。airbnbが変わったのか、私が変わったのか、どちらかは不明です。airbnbはreactでstateを直接設定するのを嫌っており、reduxが標準採用されてる予感です。

  "eslintConfig": {
    "extends": "airbnb",
    "env": {
      "browser": true,
      "jasmine": true
    },
    "parser": "babel-eslint",
    "rules": {
      "indent": [
        "error",
        4,
        {
          "SwitchCase": 1
        }
      ],
      "react/jsx-indent": [
        "error",
        4
      ],
      "react/jsx-indent-props": [
        "error",
        4
      ],
      "react/jsx-filename-extension": "off",
      "max-len": [
        "error",
        120
      ]
    }
  },

react/jsx-filename-extensionはオフにしないとimport文がうざいことになる。なんでこれオンになってるんだろう?遠回しにJSXを直接書くなってことなのかな?

goji.ioで正規表現Pattern

ハンドラのとこはgoji.ioで書いてます。見ると標準のnet/httpとgolang.org/x/net/contextをうまいこと活かしたからこその極薄なライブラリなのですが、リクエストパスでハンドラを切り替えるのに簡潔利便なので私はお好みです。しかし薄すぎて足りないところもあったのでパス切り替え判断のところ、ちょこっと補ってみました。

package patx

import (
    "regexp"
    "net/http"
    "golang.org/x/net/context"
)

type RegexpPattern struct {
    regexp *regexp.Regexp
}

func Regexp(expr string) *RegexpPattern {
    // MustCompileだと渡された表現文字列がコンパイルエラーのときpanicで止める。よろしい。
    return &RegexpPattern{regexp: regexp.MustCompile(expr)}
}

// goji.Patternインターフェイスを実装している
func (reg *RegexpPattern) Match(ctx context.Context, r *http.Request) context.Context {
    if reg.regexp.MatchString(r.URL.Path) {
        // 手抜きでMatchStringでの当り判定だけなので、積むものがなく、よってctxに何も積んでない。
        return ctx;
    }
    return nil
}

たったのこれだけで、とてつもなく手がかかってない実装ですが、そうはどっこい意外に便利。正規表現で書きたいこともある。

package main

import (
    "book"
    "goji.io"
    "goji.io/pat"
    "net/http"
    "patx"
)

func main() {
    mux := goji.NewMux()
    // 拡張子6種を簡単な正規表現でマッチさせてhttp.FileServerへルーティングする
    mux.Handle(
        patx.Regexp(`(.*\.(css|ttf|gif|png|jpg|js))$`),
        http.FileServer(http.Dir("public"))
    )
    mux.HandleFuncC(pat.Get("/book/list"), book.ListBookJSON)
    mux.HandleFuncC(pat.Get("/book/:title"), book.ListPageJSON)
    // こっから先はGoは静的HTML出すだけで、reactアプリになる
    mux.HandleFuncC(pat.Get("/*"), book.IndexHTML)
    http.ListenAndServe("localhost:8080", mux)
}

こんな感じです。GAEだと、こんな取り込みはいらず、yamlファイルに正規表現書いて同じことができますけど、平場のGOだと自分で書く。

なんで平場のGoで書かないといけないのか

GAE/GoのSDKでSierra対応版がまだ出てこないから。仮想でやるのは重いので緊急避難。GAE機能つかってるところはダメでも、そこらへんだけ殺してモック的に動かしておいて、当面はクライアントのところだけを書いてましょう。

後に気がつきましたが、macOS 10.12.1+go1.7.3 darwin/amd64環境下でGoのデバッガが「could not launch process: could not get thread count」と吐いてクラッシュしてしまいます。インクリメンタルデバッグはできない。実行はできるので、その延長線にてユニットテストはできる。

Goの正規表現

Syntax · google/re2 Wiki · GitHub

こちらの仕様とのことらしく。named captureができないなーと思ってたら、?P<name>が正解で?<name>は非サポートだった。。。

可搬性の高いGoのVendoring環境整備

GAE/Goでも、package main/func mainのGoでも、プロジェクトはdirenvをつかってVendoringを実現すると可搬性高くて最高というメモ。

direnv

シェルの実行パスに置かれたローカル設定を動的に反映させてくれるかしこいツール。素敵。インストールはHomeBrewで可能。マメとしてはGoで書かれてます。

http://direnv.net/

.bash_profile

# ~/.bash_profile
eval "$(direnv hook bash)"

direnvでシェルを監視します。私はmacOSbashなので上記のとおり。

.envrc

コントロールしたいフォルダに.envrcって名前の設定ファイルを置く。今はこんなの書いています。書いて配置したあと、一回はコンソールでフォルダにはいって、$ direnv allow って打たないといけない。

# ~/Projects/bookreader/.envrc
export GOPATH=$PWD/vendor:$PWD
# export PATH=$PWD/vendor/bin:$PWD/bin:$PATH

GOPATHは最初に、./vendor で次に ./ と二つをこの順番で通す。PATHはそれぞれのbinだけどGAEだと ./binはでてこないし、私の使い方では ./vendorの下にもbinがでてきたことない。gofmtとかgoimportとかビルド時ツールがプロジェクト内で管理されてたりすると必要かな。

# ~/Projects/.envrc
export PATH=~/go_appengine_sdk:$PATH

GAE SDKのパス設定は適宜上位フォルダに.envrcを置いて通している。フォルダツリーに従ってちゃんと.envrcの設定がカスケードされる。こうすると、ローカルPCで動かすGoとGAE/Go向けGoとクロスコンパイルするGoとで同じPCかつ同じIDEを使い分けるのも簡単。

プロジェクトのフォルダ構成(GAE版)

$ tree -a -L 3 ./Projects/bookreader/
./Projects/bookreader/
├── .envrc
├── .idea
├── src
│   ├── app.go
│   ├── app.yaml
│   ├── book
│   │   ├── book.go
│   │   └── book_test.go
│   └── public
│       └── js
└── vendor
    ├── pkg
    │   └── darwin_amd64_appengine
    └── src
        ├── goji.io
        └── golang.org

こんな感じ。GOPATHはプロジェクトのルートより優先でvendorフォルダを指定しているので、goapp getでもgo getでもvendorフォルダに格納されて自分のコードと混在しない。gbとかの専用Vendoringツールが必要なく綺麗。GAEのyamlファイルはルートにおいていないのでvendorの中はリンクされるものだけを取ってくるので、GAE的に危険なコードがはいってても(アプリのソースから使わなければ)問題ない。

WebStormの設定

f:id:masataka_k:20161029080135p:plain

direnvが動的に設定するからなのか、まだ何か研究が足りないのか、WebStormのほうではライブラリパスを明示的に追加しないとうまくなかった。この設定をするとコード補完からビルドやデバッグまでALL OK。

ちなみにapp.yaml

version: 1
runtime: go
api_version: go1
handlers:
- url: /(.*\.(css|ttf|gif|png|jpg|js))$
  static_files: public/\1
  upload: public/.*\.(css|ttf|gif|png|jpg|js)$
- url: /.*
  script: _go_app

省略したけど、実際のプロジェクトでは同じプロジェクト内にreact + redux + material-uiのクライアントアプリもあって、gulpによるビルドで/src/public/jsの下にコピーしてきています。

react, react-router, redux, react-redux, react-router-redux, redux-actions, redux-form, material-ui, redux-form-material-ui, axiosというスタック。開発はbabel, eslint, jasmine, browserifyで。それぞれよくできている。

Sierraの上に仮想ElCapitanを作る

およそOKだったので、本体もSierraにアップデートしてしまいました。

試した範囲でおよそ問題なかったのですが、アップデートしてから動かなくなってしまったのに気がついたのが一つ、Go for Google App EngineSDKです。こちら内部にGo 1.6.2を抱え込んでいるのですがこれがSierraに対応していなかった。Go1.7でSierra対応したのを、バックポートのGo1.6.3で解消ということで、これらの最新Not GAE環境でのGoはストレスなく動作しますが、GAEのSDKに組み込まれて取り替えられないのでアップデート待ち。 具体的には以下のような動作をおこします。

  • $ goapp serve でローカルサーバでアプリを立ち上げるのは一回目は大丈夫だけど、一度でもCtrl+Cで終了したらFatal Errorで二度と上がらない。OS再起動すると、また一度は上がる。
  • エラーログを眺めると、go-app-buildコマンドが突然のパニックで終了している。これが根本問題なんでしょうね。

問題のきりわけでEl CapitanをSierra上に作りました。手順はSierraでやった通りのままなので一度経験している強みから、悩みも不安もなく迅速に進みます。El CapitanのダウンロードはSierraの時と違ってとても速く、待ち時間もさほどなくすすみました。そうして仮想環境つくって操作してみたら、ちゃんとアプリの上げ下げも、ビルドもできました。終了。

仮想Sierra環境を作る

f:id:masataka_k:20160924084408p:plain

Mac環境を汚さないように壊れてよい仮想なMacを作ります。どうせいまなら出たばかりのSierraで。Mac on MacVMWareをつかうと超簡単と聞きますが、追加無償でも十分!VirtualBoxで作ります。ホストはEl Capitan。トータルで2時間半ぐらいかかりましたが、ほとんどがSierraのダウンロードと、インストールの待ち時間であり、作業は正味30分もかからなかったと思います。

Sierraのイメージを手にいれる(実績1時間以上かかった)

App Storeで「macOS Sierra」の「ダウンロード」ボタンを押してダウンロード開始。回線状態にもよるでしょうが大変時間がかかります(自宅は超高速なんですが、オフィスの回線は遅い)。ダウンロード完了したらインストーラーが起動するのをキャンセルします。/Applicationに、「Install OS X Sierra.app」が得られます。わたしはこのファイル名だったけど時期によって違うときもあるみたい?

Sierraのisoファイルを作成する(15分)

Creating a bootable El Capitan ISO image - 0xcafebabe

この記事のとおり。記事はEl CapitanですがそのままSierraに読み代えれば全てOKです。一発目のhdiutil attachのときに引数をダブルクォーテーションで囲わないとエラーです。タブキー押しでファイル名補完をその後のcpではOKですが一発目のhdiutilだけはダメ。あと、二発目のhdiutil createがちょっと時間かかって不安になりましたが、待てば良い。結果として言えることは、El CaptianとSierraでインストーラの構造などまったく一緒なんですね。

$ hdiutil attach "/Applications/Install OS X Sierra.app/Contents/SharedSupport/InstallESD.dmg" -noverify -nobrowse -mountpoint /Volumes/esd
$ hdiutil create -o Sierra.cdr -size 7316m -layout SPUD -fs HFS+J
$ hdiutil attach Sierra.cdr.dmg -noverify -nobrowse -mountpoint /Volumes/iso
$ asr restore -source /Volumes/esd/BaseSystem.dmg -target /Volumes/iso -noprompt -noverify -erase
$ rm /Volumes/OS\ X\ Base\ System/System/Installation/Packages
$ cp -rp /Volumes/esd/Packages /Volumes/OS\ X\ Base\ System/System/Installation
$ cp -rp /Volumes/esd/BaseSystem.chunklist /Volumes/OS\ X\ Base\ System/
$ cp -rp /Volumes/esd/BaseSystem.dmg /Volumes/OS\ X\ Base\ System/
$ hdiutil detach /Volumes/esd
$ hdiutil detach /Volumes/OS\ X\ Base\ System
$ hdiutil convert Sierra.cdr.dmg -format UDTO -o Sierra.iso
$ mv Sierra.iso.cdr Sierra.iso 
$ rm Sierra.cdr.dmg

VirtualBoxをインストールする(10分)

https://www.virtualbox.org/ からMacインストーラをダウンロードしてきてインストール。これもダウンロード長かった。

VirtualBoxでゲストインスタンスを作る(20分)

Mac OS X(64-bit) ですべてデフォルトにて作成すればOK。先ほど作った「Sierra.iso」を指定する。コンソールに起動ログが流れるという、いままで見たことのないMacの立ち上がり方ののちにmacOSインストーラが立ち上がる。そのままではインストール先ディスクがないので、ディスクユーティリティを起動してメインのディスクを「消去」にて、Mac OS拡張(ジャーナリング)指定。ユーティリティを終了するとインストール先として選択できるようになっている。以下の記事に後半にスクショもあって詳しい。前半のisoイメージ作るところはこちらは手作業で終わってますので飛ばします。スクショで見るディスクユーティリティとSierraのディスクユーティリティは画面が結構違うのですが、ちょっと考えればすむ程度の違いです。

VirtualBoxにOS X Yosemiteをインストールする – OTTAN.XYZ

ゲストのSierraからホストのフォルダを見えるようにする(1分)

ゲストへホストから色々とブツを送り込むために、フォルダ共有をかける。以下の記事の説明どおりにSMB共有をかける。

How to Access Host's Shared Folder from MacOSX Guest

記事で「I think the only issue that you want to ask is "why the server address is 10.0.2.2?".」というように、10.0.2.2につなぐってのが驚いたけどたしかに問題なく接続完了。OK!

App Storeでシステムアップデート(40分)

ゲストのApp Storeでアップデート検索をする。iTunes 12.5.1他ありました。これまたダウンロードが長い。

ところでSierraって

いろいろ試してみたら、不具合なく動くものばかりでしたが、私は唯一!愛用のキーアサインメントを変更するツールである「Karabiner」が最新の10.21.0でもSierraは対応していなく動きませんでした。サイトを見るとまだ対応していないって書いてあります。

Karabiner - Software for OS X

macOS Sierraサポート状況 Karabinerは今のところmacOS Sierraでは動作しません。 Sierra対応は、まずは単純なキーの変更を行える機能をKarabiner-Elementsとして開発中です。 (設定画面を除くとSierra上で動作しています) Karabinerのフル機能のSierra対応はKarabiner-Elementsが完成してから対応します。

ということで、Karabiner-ElementsへGO。英語キーでの左右Commandキーの空押しに英数/かなを割り当てるという一点だけなのですが、これがないと死ねる。

Karabiner-Elementsの設定

GitHub - tekezo/Karabiner-Elements: The next generation Karabiner for macOS Sierra

.karabiner.d/configuration/karabiner.json の内容を書いてみた。キーの空押し対応はドキュメントになかったのでとりあえず左commandはコピー・ペースト・編集戻しと多用しますので割り当てず、左optionで代用。commandもoptionも二個あるからなんとでもなる。

{
    "profiles": [
        {
            "name": "Default profile",
            "selected": true,
            "simple_modifications": {
                "left_option": "japanese_eisuu",
                "right_command":"japanese_kana"
            }
        }
    ]
}

追記:⌘英かな

GitHub - iMasanari/cmd-eikana: 左右のコマンドキーを単体で押した時に英数/かなを切り替えるようにするアプリ。その他キーのリマップも可能。

commandキーの空押し問題で調べてたら、これをみつけました。これでいい!