kawasin73のブログ

技術記事とかいろんなことをかくブログです

Webpackerを過去の遺物(1.2)から現代(3.5.5)にバージョンアップする

この記事は、Ruby on Rails その2 Advent Calendar 2018 の 22 日目です。

1 年半前の遺物

約 1 年半前に作った Rails プロジェクトがありました。

受託で 2017 年 4 月に僕 1 人で開発をはじめ、その年の 6 月に僕 1 人で開発を終えたプロジェクトです。

当時は、Rails5.1.0.rc1 から 5.1.0 にアップデートされてリリースされ、webpacker は 1.1 から 1.2 にアップデートされたあたりの時期でした。

その 6 月に開発を終えて heroku にデプロイされたそのアプリはその後大きな改修もなく 放置され 粛々と運用されていました。

その後新しい機能の追加を行う予定がなかったため放置していたプロジェクトですが、突然そのアプリをベースにして新しいビジネスを始めることになりました。

そして、1 年半前の遺物の開発が再び動き出したのです。

1 年半前の遺物を再開発する

2018 年 12 月現在、Rails5.1.0 から 5.2.2 に進化し、webpacker に至っては 1.2 から 3.5.5 まで進化していました。

ライブラリの古いバージョンを使い続けることは、ライブラリの脆弱性のリスクのみならず、最新の開発手法を利用できないために開発効率が上がらず開発スピードの低下にも繋がります。

特にフレームワークである Rails や webpacker では開発効率の低下の影響が大きいです。

時代に取り残されたプロジェクトを最新のバージョンに追随させる作業を 2 日間やっていましたが、その中で一番の大敵だった webpacker1.2 から 3.5.5 へのアップデートの詳細をここで紹介したいと思います。

webpacker とは

webpacker は、Rails で webpack を使ったフロントエンド環境の構築をサポートしてくれるライブラリです。

webpacker は Rails 向けの webpack の設定ファイルの自動生成や、webpack でビルドされた js ファイルや css ファイルと Rails の View テンプレートをバインディングしてくれるヘルパーの提供などをするライブラリです。

webpack をはじめとしたモダンな Web フロントエンド開発環境を簡単に Rails の中で使えるようになる便利なライブラリです。

ただ、webpack の設定を隠蔽しているため細かい調整をしようとすると逆にややこしくなったり、最新の webpack を使えないというデメリットもあり、Web フロントエンジニアのリソースが豊富な場合は素の webpack を使うというプロジェクトもあるようです。

いかんせん僕は 1 人で開発しているので webpack と Rails の橋渡しの部分の設定を任せるために webpacker を使っています。

また、heroku では webpacker を検出して自動で npm モジュールのインストールや webpack のコンパイルとアセットファイルの設定を行ってくれます。そのデプロイの容易さも heroku + Rails 環境で webpacker を使うメリットです。

webpacker 1.23.5.5 の違い

まず、webpacker をアップデートする上で設定ファイルなどのファイル構成がどう変わったのかを知る必要があります。

Rails のバージョンの差異は、RailsDiff という Web サイトで容易に確認することができますが、webpacker ではそのようなサイトは発見できなかったので自分で差異を調べることにしました。

Ruby 2.4.5 + Rails 5.1.0 + webpacker 1.2Ruby 2.4.5 + Rails 5.1.0 + webpacker 3.5.5 のそれぞれで rails new をしてプロジェクトを作成してそのファイル構成の差分を取ることにしました。

プロジェクトの作成には、docker で rails の開発環境を作成する自前の rails_docker_template を利用しました。( Rails プロジェクトを Docker で開発したい場合は参考にしてみてください。)

それによって2つのバージョンの rails new を試してみました。内容は以下のリポジトリにアップロードしてあります。

github.com

以下のスクリプトでプロジェクトを作成しました。

$ script/init

// webpacker のバージョンを指定してインストール

$ bin/rails webpacker:install
$ bin/rails webpacker:install:react

git diff の結果は以下の通りです。

Diff between 1.2 and 3.5.5 · Issue #1 · kawasin73/webpacker_diff · GitHub

$ git diff ruby-2.4.5-rails-5.1.0-webpacker-1.2 ruby-2.4.5-rails-5.1.0-webpacker-3.5.5 --name-only
.babelrc
.gitignore
.postcssrc.yml
Gemfile
Gemfile.lock
bin/webpack
bin/webpack-dev-server
config/environments/development.rb
config/environments/production.rb
config/secrets.yml
config/webpack/configuration.js
config/webpack/development.js
config/webpack/development.server.js
config/webpack/development.server.yml
config/webpack/environment.js
config/webpack/loaders/assets.js
config/webpack/loaders/babel.js
config/webpack/loaders/coffee.js
config/webpack/loaders/erb.js
config/webpack/loaders/react.js
config/webpack/loaders/sass.js
config/webpack/paths.yml
config/webpack/production.js
config/webpack/shared.js
config/webpack/test.js
config/webpacker.yml
package.json
yarn.lock

ただ git diff をしても CLI ではその差異がわかりにくかったので、SourceTree という GUI の git クライアントアプリでその差分を見てみました。

f:id:kawasin73:20181222230438p:plain

なるほど。

これで設定ファイルのどれが不要になってどれが追加され、どれが中身が変更されたのかがわかりました。

公式のアップデートコマンド

webpacker の README ( https://github.com/rails/webpacker#upgrading )でバージョンアップするためのコマンドが紹介されています。

bundle update webpacker
rails webpacker:binstubs
yarn upgrade @rails/webpacker --latest
yarn add webpack-dev-server@^2.11.1

# Or to install a latest release (including pre-releases)
yarn add @rails/webpacker@next

これを、webpacker 1.2 のプロジェクトに対して実行し 3.5.5 までアップデートしてみました。その差分は以下のコミットです。

Upgrade webpacker to 3.5.5 · kawasin73/webpacker_diff@c41fc94 · GitHub

これによると以下のことがわかります。

  • bin/webpackbin/webpack-dev-server というスクリプトの中身が変更されており rails webpacker:binstubs を実行することによって上書き変更される
  • @rails/webpacker という謎の npm パッケージが使われるようになっている。
  • webpack-dev-server2.11.1 でバージョン固定されている。
  • 設定ファイルの追加や削除、修正はされない

謎の npm パッケージ @rails/webpacker

ここで、@rails/webpacker という謎の npm パッケージがインストールされていることが発覚しました。その中身は以下の開発支援用のパッケージの集合です。

@rails/webpacker - npm

Dependencies (25)

  • babel-core
  • babel-loader
  • babel-plugin-syntax-dynamic-import
  • babel-plugin-transform-class-properties
  • babel-plugin-transform-object-rest-spread
  • babel-polyfill
  • babel-preset-envcase-sensitive-paths-webpack-plugin
  • compression-webpack-plugin
  • css-loader
  • extract-text-webpack-plugin
  • file-loader
  • glob
  • js-yaml
  • node-sass
  • optimize-css-assets-webpack-plugin
  • path-complete-extname
  • postcss-cssnext
  • postcss-import
  • postcss-loader
  • sass-loader
  • style-loader
  • uglifyjs-webpack-plugin
  • webpack
  • webpack-manifest-plugin

Dev Dependencies (6)

  • eslint
  • eslint-config-airbnb
  • eslint-plugin-import
  • eslint-plugin-jsx-a11y
  • eslint-plugin-reactjest

このパッケージのほとんどは webpacker 1.2 では package.json に直接記載されてインストールされていたものです。それを @rails/webpacker にまとめたものであると理解しました。

しかし、開発支援用のパッケージのうちビルドに必要なパッケージがが Dev Dependencies ではなく Dependencies としてインストールされているのはちょっと気持ち悪いです。

この理由は以下の issue で議論されている通り production 環境で js ファイルのビルドをできるようにするために Dependencies に配置されているようです。

Webpack executable not found on production · Issue #117 · rails/webpacker · GitHub

ここを独自にわざわざ Dev Dependencies に変更して本来の Dependencies のあり方を求めるより、Rails way にのっとってパッケージ管理を webpacker の内部に隠蔽して開発をスムースに進めることのメリットが大きいと判断して @rails/webpacker パッケージをそのまま使うことにしました。

アップデートの方針

アップデートは以下のように行うことにしました。設定ファイルの更新は自動ではやってくれないので自分で1ファイルずつ編集していきます。

webpacker のアップデート

Gemfile の webpacker3.5.5 にあげてアップデートします。

$ bundle update --conservative webpacker

アップデートコマンドで上書きするファイル

$ bin/rails webpacker:binstubs

このコマンドを実行することで以下のファイルが更新されます。

  • bin/webpack
  • bin/webpack-dev-server

追加するファイル

以下のファイルを追加します。ファイルの内容は webpacker 3.5.5 でビルドしたもの からコピーします。

  • config/webpack/environment.js
  • config/webpacker.yml

削除するファイル

以下のファイルを削除します。

  • config/webpack/configuration.js
  • config/webpack/development.server.js
  • config/webpack/development.server.yml
  • config/webpack/loaders/assets.js
  • config/webpack/loaders/babel.js
  • config/webpack/loaders/coffee.js
  • config/webpack/loaders/erb.js
  • config/webpack/loaders/react.js
  • config/webpack/loaders/sass.js
  • config/webpack/paths.yml
  • config/webpack/shared.js

修正するファイル

以下のファイルを 1.23.5.5 の差分をみて修正していきます。差分は Diff between 1.2 and 3.5.5 · Issue #1 · kawasin73/webpacker_diff · GitHub にまとめてありますが、SourceTree などの GUI アプリで差分を見た方がわかりやすいと思います。

  • .babelrc
  • .gitignore
  • .postcssrc.yml
  • config/webpack/development.js
  • config/webpack/production.js
  • config/webpack/test.js
  • config/environments/development.rb
  • config/environments/production.rb

package.json

1.23.5.5 の差分をみて package.json から @rails/webpacker パッケージにまとめられるパッケージを削除します。( Diff between 1.2 and 3.5.5 · Issue #1 · kawasin73/webpacker_diff · GitHub

そのあと、以下のコマンドで @rails/webpacker をインストールし、webpack-dev-server@^2.11.1 をインストールします。

$ bin/yarn add @rails/webpacker
$ bin/yarn add webpack-dev-server@^2.11.1

以上で webpacker のアップデートは完成です。

最後に

ここまで慎重に調査するのに疲れましたが、なんとか webpacker を 1.2 から 3.5.5 にアップデートできました。

これは、プロジェクトを作った時に webpacker の設定を独自に変更せずにそのまま使っていたのも功を奏していると思います。

おそらく、webpacker の 1.2 なんていう過去の遺物を使っているプロジェクトは少ないんじゃないかと思いますが、アップデートする時は参考にして見てください。

現場からは以上です。

Mac で起動可能な Windows 10 の USB イメージを作る

この記事は、PSI(東京大学工学部システム創成学科知能社会システムコース) Advent Calendar 2018 の 22 日目です。

はじめに

東大の応用プロジェクトという授業で、LabVIEW というソフトウェアを使ってセンサーアプリケーションを作ることになりました。

しかし、大学から支給される Windows PCが遅い。信じられないくらい遅い。

LabVIEW を立ち上げるのに10分とかかかります。平気で。

それが許せなくて自分のパソコンで LabVIEW をやりたくなりました。ところが、macOS しか持っていない僕はインストールできない。(LabVIEW 自体は macOS にもインストールできるみたいですが、ライセンス関係がめんどくさそう)

さらに、他の授業でも Window でしか動かないソフトウェアを使うことがあり(ここで支給される PC も遅い)、自分も Windows が使える環境があると便利なのでインストールを試行錯誤しました。

というか、東大はまともな PC を用意してくれ。

Boot Camp

MacWindows をインストールする一般的な方法として BootCamp があります。Mac を買った時からデフォルトでインストールされているアプリで、Windows をインストールしてデュアルブートを可能にしてくれます。

Boot Camp アシスタントで Mac に Windows 10 をインストールする - Apple サポート

しかし、重大な欠点があります。それは、マシンのディスクのパーティションを切ってそこにインストールする ことです。わざわざ Windows のためにマシンのリソースを汚染されたくない。

USB にブートイメージをインストールして Mac を汚染せずにポータブルな Windows USB を作りたいというのがこの記事の趣旨です。

Windows To Go

Windows を USB にインストールする手法として Windows Enterprise で提供されているらしい 「Windows To Go」 というソフトウェアがあるらしいです。しかし、Windows Enterprise 持ってないので使えない。

さらに調べると、AOMEI Partition Assistant とかいう怪しげなソフトウェアで Windows To Go という機能が搭載されていて USB に Windows をインストールできるらしい。便利!しかも、Standard 版は無料!

Windows 10 To Go USBを作成してポータブル作業環境を取得します

早速インストールしてみたら、Windows To Go は有料版でないといけないらしい。5000円くらいするけど早速買ってやってみました。

が、Mac で起動しても途中で落ちて macOS にフォールバックしてしまい、役には立ちませんでした。クソが

90 日間返金可能ということなので返金してもらいました。(英語の対応だったけど如何に役に立たなかったかを説明したらちゃんと返金してくれた。)

自分でゴリゴリやる

色々思考錯誤したり、記事を探したりする中でこの記事に出会いました。

ohaohaoha.cocolog-nifty.com

ここで紹介されている方法を参考にやったらうまくいきました。マジでありがとうございます。

ちなみに、この記事で紹介されていたのは Windows マシンでインストールする方法ですが、今回は Mac しか持っていない人向けにアレンジしています。

諸事情あって 2 回 Windows をインストールしたのですが、1回目はこの記事の通り Windows マシンでインストールして、2 回目は Mac だけでインストールしたので、正しく動くことは確認しています。

必要なもの

Mac

まず、何らかの Mac が必要です。

僕の Macbook Pro のスペックはこんな感じです。

f:id:kawasin73:20181217222428p:plain

USB メモリ

次に USB が 2 本必要です。 1 本はインストーラ用、もう 1 本が Windows ポータブル USB になる本番用です。

インストーラ用には、以下の 64 GB の USBを

本番用には、同じ USB の 128 GB のやつを買いました。

2本ともまぁまぁ高いですが、書き込み性能が 100MB / s 以上出るので OS として使うために必要な経費ということで泣く泣く買いました。 Windowsインストーラは 4 GB くらいあるのですが、高速な書き込み性能を持っているのでインストールも比較的早く終わりました。

ぶっちゃけ、インストーラ用の USB は本番用の USB が完成したら必要ないので、30GB くらいの安いものでもいいと思います。(コピーが遅いかもしれないけど)

あと、2本の USB は容量が違ったほうが見分けがついて便利です。

Windows ライセンス

そして Windows の ISO ファイルとライセンス が必要です。

Windows のライセンスは調べてみると 1 万円は超えるみたいです。高い。

ただ、東京大学Microsoft 製品を無償で学生に提供していて、Windows のライセンスもあったのでそれを利用してライセンスを取得しました。

UTokyo Microsoft Windows 10 for students | 東京大学

ISO ファイルは、東大のライセンスを購入したページにしたがってダウンロードしました。

それ以外の場合は、以下のリンクからダウンロードできるようです。

Windows 10 のディスク イメージ (ISO ファイル) のダウンロード

今回は、Microsoft Windows 10 Education をインストールしましたが、他のエディションでも基本的にインストール方法は変わらないと思います。

根気

ディスクコピーには時間がかかりますし、オペミスでディスクを失う可能性もあるので細心の注意が必要です。

根気強くやっていきましょう。

免責

ディスクを触るコマンドがあるので自己責任でお願いします。

インストール手順

大まかな手順は以下の通りです。

  1. macOSインストーラ用 USB をフォーマット
  2. インストーラ用 USB にインストーラモードの Windows をコピー
  3. インストーラ用 USB から Windows を起動
  4. 本番用 USB を diskpartwindows のコマンド)を使ってフォーマット
  5. 本番用 USB に Windows 10 をインストール
  6. macOS に切り替えて、Boot Camp Windows サポートライブラリ をダウンロードし、インストーラ用 USB にコピー
  7. 本番用 USB で Windows 10 を起動
  8. Windows 10 に Mac 用の様々なドライバーを Boot Camp Windows サポートライブラリ を使ってインストール
  9. Windows 10 のライセンス認証

かなり長いですが、頑張って行きましょう。

1. macOSインストーラ用 USB をフォーマット

macOSインストーラ用 USB を差した状態で、ディスクユーティリティ アプリを開きます。

USB を選択した状態で上にある 消去 ボタンをクリックし、以下の項目を入力して 消去 ボタンを押して USB をフォーマットします。

  • 名前:Windows(これは別の適当なものでもいいです)
  • フォーマット:exFAT(インストールするファイルサイズが大きいのでこれが必要です)
  • 方式:マスター・ブート・レコードGUID パーティションマップ でも大丈夫だと思いますが、今回はこっちで)

f:id:kawasin73:20181217231520p:plain

注意としてこれによって USB ディスクの中身は全て失われます。また、間違って別のディスクを消去することがないように 気をつけてください。

2. インストーラ用 USB にインストーラモードの Windows をコピー

Finder で Windows の ISO ファイルをダブルクリックして開きます。

ISO ファイルの中身が表示されるので、中身を全てコピーして、フォーマットした インストール用 USB の一番上のルートディレクトリにペーストします。この操作は Finder アプリ上で行います。(dd を使ってやってもうまくいきませんでした。)

かなり内容が大きい上に多いので時間がかかります。僕の環境では、10 分くらいかかりました。その間に洗濯物でも干しておきましょう。

3. インストーラ用 USB から Windows を起動

ここで一旦 Mac をシャットダウンします。

そして、インストーラ用 USB だけを差した状態で alt キー(または option キー)を押しながら起動します。音がするまで alt キーを押していると起動ディスクを選択する画面が表示されます。

オレンジ色の EFI Boot を矢印キーで選択して Enter キーを押してインストーラWindows を起動します。

f:id:kawasin73:20181217233132j:plain

Windows が起動するはずです。初期化が終わると以下のような画面が表示されるので 次へ ボタンを選択します。

f:id:kawasin73:20181217233519j:plain

ちなみ画面サイズがバグってるので文字がとても小さいです。この文字が小さい地獄はステップ 8 で Macデバイスドライバをインストールするまで続きます。

「今すぐインストール」のページが表示されたら、今すぐインストールせずに左下の コンピューターを修復する をクリックします。

f:id:kawasin73:20181217233941j:plain

トラブルシューティング を選択し、

f:id:kawasin73:20181217234458j:plain

コマンドプロンプト を起動します。このコマンドプロンプトは管理者権限で起動されます。

f:id:kawasin73:20181217234558j:plain

4. 本番用 USB を diskpart を使ってフォーマット

ここで、本番用 USBMac に差し込みます。

diskpart とは、Windows のディスクをフォーマットするコマンドです。(多分)

起動したコマンドプロンプトdiskpart と入力して Enter を押すと diskpart の画面に切り替わります。

diskpart

diskpart の画面では、先頭に DISKPART> が表示されます。これ以降のこの節のコマンドは、diskpart の中でのコマンド実行です。

list disk コマンドでディスクの一覧を表示し、本番用 USB のディスク番号を確認します。USB のディスクサイズが違っているとわかりやすいです。

select disk <number> でディスクを選択します。僕の環境では、3 番でした。

list disk をもう一度実行し、選択したディスクの左側に白い * が表示されて正しく選択できていることを確認します。

もし、異なるディスクを選択していた場合 Mac のディスクが吹っ飛ぶ可能性があるので十分注意してください

list disk
select disk 3
list disk

clean で選択されたディスクを初期化します。

create partition primary size=350ブート用パーティション領域350 MB)を作った後、create partition primary で残りの領域のパーティションを作成します。

create partition するときに、エラー(DiskPart は最新の状態でないオブジェクトを参照しています・・・・)が発生することがあります。その時は、diskpart で rescan を実行して、最初の list disk からやり直して下さい。

それでもうまくいかない場合は、exit で diskpart を終了して、再度 diskpart を実行するところからやり直してください。(それでもうまくいかない場合は知らん

clean
create partition primary size=350
create partition primary

list partitionパーティションを確認し、パーティションが 2 つ作成されていることを確認します。

select partition350 MB のブートパーティションを選択し、format fs=fat32 quick でフォーマットし、active でアクティベートし、assign letter=b で、B ドライブとして設定します。

次に、select partition 2 でメインパーティションを選択し、format fs=ntfs quick でフォーマットし、assign letter=o で、O ドライブとして設定します。

list partition
select partition 1
format fs=fat32 quick
active
assign letter=b

select partition 2
format fs=ntfs quick
assign letter=o

雰囲気としてはこんな感じです。(画質が悪くてすみません)

f:id:kawasin73:20181218000435j:plain

これで、本番用 USB のフォーマットは完成です。exit で diskpart を抜けます。

exit

5. 本番用 USB に Windows 10 をインストール

そのまま、コマンドプロンプトWindows 10 をインストールしていきます。

Windows のイメージファイルは、install.wim か、install.esd というファイルです。どちらでも同じようにインストールすることができるらしいです。

僕の環境では install.wim だったので、それがない場合は install.esd と読み替えてください。

まず、install.wim を探します。install.wim はステップ 2 でコピーした ISO ファイルの sources ディレクトリの下にあります。まず、コピーした USB のディスクドライブを探します。

wmic logicaldisk get caption で利用可能なディスクドライブの一覧が表示されます。

wmic logicaldisk get caption

Caption
B:
C:
D:
O:
X:

BO は、さっき作ったドライブで、X は現在いるドライブです。僕の環境では、C ドライブに install.wim がありました。dir C:¥sources¥ でファイルの一覧が表示されるので install.wim を目グレップで探しましょう。

違う場合は、別のドライブにあるはずです。

dir C:¥sources¥

Windows のイメージを本番用 USB にインストールします。(Dism /Apply-Image /ImageFile:C:¥sources¥install.wim /Index=1 /ApplyDir:o:¥)コマンドの C ドライブは、発見したドライブ名に差し替えてください。

Index の番号が不安になりますが、これは複合 Windows イメージの何番目のイメージを使うかということを表すらしいので 1 で大丈夫だと思います。(参考:Windowsイメージ操作まとめ - Qiita

これも時間がかかります。僕の環境では 15 分くらいかかりました。この間に最後のステップに備えてシャワーでも浴びておきましょう。

Dism /Apply-Image /ImageFile:C:¥sources¥install.wim /Index=1 /ApplyDir:o:¥

最後にブートパーティション領域を初期化します。

o:\Windows\System32\bcdboot o:\Windows /l ja-JP /s b: /f ALL

/ll は、小文字の L です。見分けがつかなくて一回 Itypo しました。ややこしい。

以上で Windows のインストールは完了です!お疲れ様でした!

コマンドプロンプトexit で抜けて、シャットダウンしましょう。

exit

6. macOS に切り替えて、Boot Camp から Windows サポートライブラリ をダウンロードし、インストーラ用 USB にコピー

あ、完成したと思いました?残念。まだあります。

このまま Windows を起動することはできますが、Mac のハードウェアのデバイスドライバがないので wifi に繋げません。外部ネットワークにアクセスできないので、ライセンス認証もできないですし、デバイスドライバーをダウンロードすることすらできません。残念でした。

ここで、起動するのは Windows ではなく macOS です。(macOS が起動しない場合は、alt キーを押したまま電源を入れ Macintosh HD を選択して起動してください)

Windows のための MacデバイスドライバBoot CampWindows サポートライブラリ として提供しているのでそれを利用します。

Mac に Windows サポートソフトウェアをダウンロードしてインストールする - Apple サポート

Boot Camp アプリを起動して、メニューバーにある アクション をクリックし、Windowsサポートソフトウェアをダウンロード をクリックします。

f:id:kawasin73:20181218010521p:plain

場所を指定してダウンロードします。全部で 2.8 GB くらいあるので気をつけてください。

ダウンロードが終わったら、インストーラ用 USB にダウンロードした WindowsSupport ディレクトリをコピーします。コピー先はどこでも大丈夫なので一番上がいいんじゃないでしょうか。

インストーラ用 USBの Windows イメージはもう使わないので、コピーする前にインストーラ用 USB を消去してフォーマットし直しても大丈夫です。

コピーが終わったら macOS をシャットダウンしてください。

7. 本番用 USB で Windows 10 を起動

インストーラ用 USB を抜いて、本番用 USB を差してください。いよいよ Windows を起動します。

alt キーを押しながら Mac の電源を入れてインストーラの時と同じようにオレンジ色の EFI Boot を選択して Windows 10 を起動します。

Windows の初期化画面が出てくるので、みなさんのお好みの設定で初期化を進めてください。

ただ、ネットワークの設定はデバイスを認識しないため、スキップしてください。

初期化が終わると Windows 10 が起動します。

8. Windows 10 に Mac 用の様々なドライバーを Boot CampWindows サポートライブラリ を使ってインストール

Windows 10 が起動したら、インストーラ用 USB を差してください。

エクスプローラで(Mac の Finder みたいなアプリ)インストーラ用 USB の中身を開いてステップ 6 でコピーした WindowsSupport を探します。

WindowsSupport¥BootCamp¥Setup をダブルクリックして実行すると Macデバイスドライバがインストールされます。

インストール中に画面サイズがいい感じになって感動すると思います。

インストールが終わると再起動するのでなされるがままに再起動しましょう。

9. Windows 10 のライセンス認証

最後に Windows 10 のライセンス認証をします。

スタートメニュー (左下のウィンドウズアイコン)をクリックし、設定(左側の歯車マーク)をクリックします。

更新とセキュリティ を開き、ナビゲーションバーから ライセンス認証 を表示します。

ライセンス認証をするために プロダクトキーを変更する をクリックして、ライセンスキーを入力します。

これで、晴れてポータブルな Windows 10 の USB ドライブが完成しました。お疲れ様でした。

使い方

これからは好きな時に Windows を使えます。

Mac に USB を差した状態で alt キーを押しながら電源を入れると OS を選べます。

alt キーを押さないで起動した場合は前回起動した OS が起動します。

USB が刺さっていない時は macOS が起動します。

簡単ですね。

何で2回もインストールしたか

これは後日談なのですが、冒頭で諸事情により2回 Windows をインストールしたと書きました。

諸事情あって 2 回 Windows をインストールしたのですが、1回目はこの記事の通り Windows マシンでインストールして、2 回目は Mac だけでインストールしたので、正しく動くことは確認しています。

これは、1回目は 64 GB のUSB メモリに Windows 10 をインストールして授業に挑んだわけですが、LabVIEW がインストールするのに全部で 45GB を必要とするということが発覚し挫折したからです。

てか、45 GB ですよ!!!!奥さん!!!信じられますか????

64GB のうち Windows をインストールした後に空き容量が 20 GB くらいしか残っていなかったので、泣く泣く 128 GB の USB を買ってインストールし直しました。

以上恨みつらみでした。

最後に

かなり長くなりましたが、お疲れ様でした。ぜひポータブルな Windows USB ドライブライフを試してみてください。

Go でビットベクトルライブラリを作った話

この記事は、DMM.com Advent Calendar 2018 の 15 日目の記事です。

Go 言語でエンディアン(ビッグエンディアン、リトルエンディアン)を指定できるビットベクトルライブラリを作ったのでその紹介をします。

このライブラリは、mmap でファイルがマッピングされたメモリを直接扱うことを想定し、ビットベクトルの中身がマシンのエンディアンに関わらず指定したエンディアンになるように操作を行います。

そのため、ファイルに保存したビットベクトルのバイト列を異なるエンディアンのマシンで扱うことができるようになります。

また、不必要なコピーとエンディアンの変換処理が発生しないように設計されています。そのために内部で unsafe パッケージを使って []byte から []uint64 へのキャストを行なって、バイト列を直接扱えるように工夫しています。

github.com

ビットベクトルとは

ビットベクトルとは、 ある非負の整数と true, false の 2 値をマッピングする 簡潔データ構造 の一種で、空間的にも計算量的にも効率良く動作します。


ある非負の整数とブーリアン型とのマッピングデータ構造の一例として例えば []bool が考えられます。

Go 言語においてブーリアン型は 1 バイトのメモリを必要とし、[]boolN 個の bool 値を保存するのに N バイトを消費 します。(厳密にはスライス構造体もメモリを消費しますが、ここでは無視します。)

fmt.Println(unsafe.Sizeof(true)) // 1

boolVec := make([]bool, N)

// N bytes for N items
// [ true | false | false | true | true | false | false | false | .... ]

i 番目の値を引いてくる計算は、スライスのインデックスを渡すことで一発で引いてくることができます。

boolVec[i] // true or false

0 ~ N の値の中で true がセットされていない最小の数値を見つける計算( Find First Zero )は、最悪で N 回のループを処理します。

for i := 0; i < N; i++ {
    if !boolVec[i] {
        // i is false!
    }
}

一方、ビットベクトルは true, false の値を 1 ビットの 1, 0 として表し、N 個の値を保存するのに N / 8 バイトを消費 します。つまり、メモリ空間の消費量は 1/8 に圧縮 されます。

// N / 8 bytes for N items
[ 10011000 ... ]

また、ビットベクトルは 64 ビットの符号なし整数の配列 []uint64 で表されます。

bitVec := make([]uint64, (N + 63)/64)
[ 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
  ....]

i 番目の値は、[]uint64i / 64 番目の 64 ビットのうち下から i % 64 ビット目のビットが 01 かどうかを調べることでわかります。

実際には割り算の計算は重いため、ビットの操作によって軽く計算します。

  • i / 64i6 ビット右シフトさせたもの(i >> 6
  • i % 64i の下位 6 ビット(i & 63
  • i % 64 ビット目のビットマスク:1 << (i & 63)
bitVec[i >> 6] & (1 << (i & 63)) != 0 // true or false

0 ~ N の値の中で true がセットされていない最小の数値を見つける計算( Find First Zero )は 64 ビットごとに検索し、N / 64 回のループ処理で検索することができます。つまり、計算量は 1 / 64 に圧縮 されます。

これは、CPU が一度に 64 ビットずつ計算を行うためです(64 ビット CPU アーキテクチャの場合。32 ビット CPU アーキテクチャの場合は 32 ビットずつです)。

このように、ビットベクトルを使うと true, false の値を空間的にも計算量的にも効率よく保存できます。

ビッグエンディアン、リトルエンディアンとは

ここまででビットベクトルの説明をしたわけですが、ファイルにビットベクトルを書き出す時にはエンディアンを意識する必要があります。

エンディアン( Endianness )とはバイトの並び順のことで、ビッグエンディアン( Big Endian )とリトルエンディアン( Little Endian )の2種類があります。(ミドルエンディアンなど他のエンディアンもあるらしいですがごく少数派らしいです)また、バイトオーダーとも呼ばれます。

ビッグエンディアンは上位のバイトから並べていく方式で、リトルエンディアンは下位のバイトから並べていく方式です。

例えば Go 言語の uint64 の場合 64 ビットの整数であるためこれは 8 バイトで表されます。

16進数で 0x0123456789abcdef と表される数値は、ビッグエンディアンでは 0x01 | 0x23 | 0x45 | 0x67 | 0x89 | 0xab | 0xcd | 0xef と、リトルエンディアンでは 0xef | 0xcd | 0xab | 0x89 | 0x67 | 0x45 | 0x23 | 0x01 と表されます。

エンディアン - Wikipedia

エンディアンは CPU のアーキテクチャによって変わります。よく使われている amd64 のCPU(IntelAMD などの CPU)では、リトルエンディアンです。

また、CPU によってはビッグエンディアンとリトルエンディアンを指定できる CPU もあるみたいです。

同一プロセスのメモリ上の値は全て同じエンディアンで扱われているため、通常のメモリ上の値を操作するプログラミングをする場合はエンディアンを気にする必要はありません。

しかし、マシンをまたいでバイナリデータをやり取りする場合はこのエンディアンに注意しないと、マシンによって全く異なる値に認識されてしまうことになります。(TCP/IP などのネットワークプロトコル操作やバイナリエンコードされたファイルを異なるマシンで扱うときなど)

Go言語でのビットベクトルライブラリ

ここまでで、ビットベクトルとエンディアンの説明をしました。ここからが本題です。

Go 言語にはすでにビットベクトルのライブラリとして、github.com/willf/bitset があります。

github.com

Go のバージョン 1.7 から対応しているとても良いライブラリなのですが、外部のメモリを直接扱えず willf/bitset 内で管理している []uint64 にコピーする必要があるという欠点があります。

willf/bitset はバイト列ストリームへエンディアンを指定した書き出しと読み込みをする WriteTo()ReadFrom() インターフェイスを提供しており、これによりファイルへの書き出しと読み込みが可能になります。

しかし、ファイルへの書き出しでは変更された部分だけではなく全てを書き出すため、必要のないコピーが発生します。また、初期値のロードでは必ずカーネルランドからユーザーランドへのメモリコピーが発生します。

willf/bitset は内部のメモリ []uint64 をマシンのエンディアンで処理します。そして、書き出しと読み込み時に内部で binary.Write()binary.Read() を使い指定されたエンディアンに変換します。エンディアンの変換ではマシンのエンディアンに関わらず 8 バイトごとに変換処理をするため、頻繁にファイルと同期をとる場合はこの 変換のコストコピーのコスト が大きくかかってきます。

ビットベクトルライブラリを作る

ファイルへの書き出しと読み込みの メモリコピーのオーバヘッド と、エンディアンの変換処理のオーバーヘッド を極力抑えるために github.com/kawasin73/bitset というビットベクトルのライブラリを作成しました。

github.com

このライブラリは外部から提供されたバイト列 []byte を直接扱います。

mmap はファイルがマッピングされたカーネル側のメモリをユーザーランドから直接操作することができる機能です。

kawasin73/bigset では mmap によってファイルがマッピングされたメモリも直接扱うことができるため、初期値のロードのメモリコピーが発生せず、ファイルへの書き戻しのメモリコピーも必要なくなります。

また、ビットベクトルの操作ではマシンのエンディアンによって処理を切り替えることによって、エンディアンの変換処理のオーバーヘッドをなくすことができます。

このライブラリを作る上では、https://github.com/willf/bitset を参考にして実装し、以下の点を工夫しました。

  • []byte から []uint64 へのゼロコピー変換
  • それぞれのマシンエンディアンに最適化されたビットベクトル操作

[]byte から []uint64 へのゼロコピー変換

通常 []byte から []uint64 への変換は、8 バイトずつ binary.BigEndian.Uint64() または、binary.LittleEndian.Uint64() によって変換してコピーを行います。しかし、それでは mmap されたメモリを操作することはできず、またメモリコピーのコストがかかります。

そこで、以下のようにして unsafe パッケージを使い []byte から []uint64 へコピーすることなく変換しています。

buf := make([]byte, 8 * n)
header := *(*reflect.SliceHeader)(unsafe.Pointer(&buf))
header.Len /= 8
header.Cap /= 8

vec := *(*[]uint64)(unsafe.Pointer(&header))

この手法では従来のバイト列が 8 の倍数でなかった場合、末尾のあまりの部分は無視されます。

利用者にとって想定外の領域の切り詰めが起こらないように、このライブラリでは8 の倍数でない長さのバイト列の場合は初期化時に bitset.ErrInvalidLength エラーを返すようにしています。

変換された []uint64 だけを bitset.BitSetvec として保持して元の []byte の参照を消した時、バイト列が GC されて vec の内容が別の処理に書き換えられるバグがあったため、元の []bytebitset.BitSetorig として保持して GC されないように工夫しています。

マシンエンディアンに最適化されたビットベクトル操作

このライブラリではマシンのエンディアンhostEndian)を検出し、利用者が指定したエンディアンuserEndian)と hostEndian を比較して処理を切り分けています。

hostEndianuserEndian の組み合わせは、(hostEndian, userEndian) = (LittleEndian, LittleEndian), (BigEndian, LittleEndian), (LittleEndian, BigEndian), (BigEndian, BigEndian) の全部で 4 通り あります。

しかし、リトルエンディアン と ビッグエンディアン は逆に並んでいるだけであるので、hostEndianuserEndian同じ違う かの 2 通りだけを考えればよい ということになります。

初期バージョンでは、以下の 64 ビットを 8ビットずつ反転させるスワップ関数を利用して実装しました。

func swapUint64(n uint64) uint64 {
    return ((n & 0x00000000000000FF) << 56) |
        ((n & 0x000000000000FF00) << 40) |
        ((n & 0x0000000000FF0000) << 24) |
        ((n & 0x00000000FF000000) << 8) |
        ((n & 0x000000FF00000000) >> 8) |
        ((n & 0x0000FF0000000000) >> 24) |
        ((n & 0x00FF000000000000) >> 40) |
        ((n & 0xFF00000000000000) >> 56)
}

hostEndianuserEndian が異なる時に、以下の手順で操作を行うとユーザーが指定したエンディアンでメモリ操作ができます。

  1. メモリから反転させて一時メモリに読み出し
  2. マシンのエンディアンで処理を行い
  3. 反転させてメモリに書きもどす

初期バージョンの実装とテストが終わった後に、それぞれのエンディアンが異なる時の操作を最適化された処理に書き換えています。

テスト

このライブラリはビッグエンディアンとリトルエンディアンに対応してものであると謳っている以上、それぞれのエンディアンのマシンでテストをする必要があります。

travis の上でビッグエンディアンをエミュレートしてテストする手法は以下の記事にまとめています。

kawasin73.hatenablog.com

利用方法

だいたいこんな感じで使います。(README.md からコピーしてきました。)

func main() {
    // in memory usage
    buf := make([]byte, 2*8)
    b, _ := bitset.New(buf, binary.LittleEndian)
    for _, v := range []uint{0, 1, 3, 6, 10, 64, 127, 128} {
        b.Set(v) // when v == 128 returns false because overflow
    }
    fmt.Println(buf) // [75 4 0 0 0 0 0 0 1 0 0 0 0 0 0 128]

    b.Unset(127)
    fmt.Println(b.Get(127)) // false

    for v, ok := b.FindFirstOne(0); ok; v, ok = b.FindFirstOne(v + 1) {
        fmt.Println(v) // 0 1 3 6 10 64
    }

    // File + mmap usage
    const pageSize = 4 * 1024
    f, _ := os.OpenFile("example.bit", os.O_RDWR|os.O_CREATE, 0666)
    f.Truncate(pageSize)
    buf, _ = syscall.Mmap(int(f.Fd()), 0, pageSize,
        syscall.PROT_READ|syscall.PROT_WRITE,
        syscall.MAP_SHARED)
    defer func() {
        f.Sync()
        syscall.Munmap(buf)
        f.Close()
    }()

    b, _ = bitset.New(buf, binary.BigEndian)
    for v, ok := b.FindFirstOne(0); ok; v, ok = b.FindFirstOne(v + 1) {
        fmt.Println(v) // 0 1 3 6 10 64  if executed twice
    }
    for _, v := range []uint{0, 1, 3, 6, 10, 64, 127, 128} {
        b.Set(v) // when v == 128 returns false because overflow
    }
    fmt.Println(buf) // [0 0 0 0 0 0 4 75 128 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 ....
}

今対応している操作はこのくらいなので、今後操作の種類を追加していけるといいなと思っています。

  • func (b *BitSet) Count() uint
  • func (b *BitSet) FindFirstOne(i uint) (uint, bool)
  • func (b *BitSet) FindFirstZero(i uint) (uint, bool)
  • func (b *BitSet) FindLastOne() (uint, bool)
  • func (b *BitSet) Get(i uint) bool
  • func (b *BitSet) Set(i uint) bool
  • func (b *BitSet) Unset(i uint) bool

制約

https://github.com/willf/bitset では、大きな違いとして、メモリサイズの自動拡張機能があります。ビットベクトルの範囲を超えた整数に Set があった場合は []uint64 のサイズを自動で拡張してくれる機能です。

このライブラリでは、mmap したメモリも想定しているので自動でメモリを拡張することができません。(拡張すると違うメモリ空間にビットベクトルが移動し、mmap したメモリへの変更がされなくなる)

このライブラリ自体は、mmap したメモリ以外のヒープメモリも受け付けるものなので、サイズを自動で拡張する機能があれば便利だなと考えています。

最後に

長かったですが以上です。

ビットベクトルを使いたい時にぜひ使ってみてください。

俺はビッグエンディアンでテストがしたいんだ!!!

この記事は、ソフトウェアテスト #2 Advent Calendar 2018 - Qiita の1日目です。

github.com/kawasin73/bitset という Go 言語のビットベクトルのライブラリを作りました。このライブラリはバイトオーダーがビッグエンディアンのマシンでもリトルエンディアンのマシンでも同じ振る舞いをするように実装されています。

ビッグエンディアンとリトルエンディアンのそれぞれのマシンで正しく動くことを確認するテストを Travis CI で実行する手法を試行錯誤したので紹介します。

ビッグエンディアン、リトルエンディアンとは

エンディアン( Endianness )とはバイトの並び順のことで、ビッグエンディアン( Big Endian )とリトルエンディアン( Little Endian )の2種類があります。(ミドルエンディアンなど他のエンディアンもあるらしいですがごく少数派らしいです)また、バイトオーダーとも呼ばれます。

ビッグエンディアンは上位のバイトから並べていく方式で、リトルエンディアンは下位のバイトから並べていく方式です。

例えば Go 言語の uint32 の場合 32 ビットの整数であるためこれは 4 バイトで表されます。

16進数で 0x1234abcd と表される数値は、ビッグエンディアンでは 0x12 | 0x34 | 0xab | 0xcd と、リトルエンディアンでは 0xcd | 0xab | 0x34 | 0x12 と表されます。

エンディアン - Wikipedia

エンディアンは CPU のアーキテクチャによって変わります。よく使われている amd64 のCPU(IntelAMD などの CPU)では、リトルエンディアンです。

また、CPU によってはビッグエンディアンとリトルエンディアンを指定できる CPU もあるみたいです。

同一プロセスのメモリ上の値は全て同じエンディアンで扱われているため、通常のメモリ上の値を操作するプログラミングをする場合はエンディアンを気にする必要はありません。

しかし、マシンをまたいでバイナリデータをやり取りする場合はこのエンディアンに注意しないと、マシンによって全く異なる値に認識されてしまうことになります。(TCP/IP などのネットワークプロトコル操作やバイナリエンコードされたファイルを異なるマシンで扱うときなど)

両方のエンディアンでテストする

今回作った github.com/kawasin73/bitset は、ファイルに永続化されたビットベクトルが異なるマシンで利用されるユースケースを考慮して、どのエンディアンのマシンでも指定されたエンディアンでビットベクトルを扱うようにしています。

そのため、ビッグエンディアンとリトルエンディアンそれぞれのマシンで正しく動いているかをテストする必要がありました。

今回は自動テスト環境として Travis CI を利用することにしましたが、残念ながら Travis CI は amd64アーキテクチャの CPU にのみ対応しており、CPU のアーキテクチャの変更はできませんでした。

そのため、ビッグエンディアンの環境でのテストを何らかの方法でエミュレートして実行する必要があります。

今回は、 qemu-user-static という CPU のエミュレータ を使ってビッグエンディアン環境でのテストを行うことにしました。

qemu から go test を呼び出す

qemu-user-static は様々な CPU アーキテクチャをエミュレートして実行バイナリを実行してくれるソフトウェアです。

multiarch/qemu-user-static リポジトリアーキテクチャごとのエミュレータがアップロードされているため、そこからダウンロードして利用することにしました。(apt-get などでインストールできますが詳しくは後述)

以下のように qemu-<cpu architecture>-static の引数にそのアーキテクチャ用の実行バイナリを渡すことで実行できます。

# ppc64 アーキテクチャの場合
$ qemu-ppc64-static ./some-ppc64-binary

go 言語自体(go buildgo test などを行うツール群)は、Linux OS では amd64, 386, arm, arm64, s390x, ppc64le で提供されていますが、このうちビッグエンディアンなのは s390x のみです。(Getting Started - The Go Programming Language

しかし、qemu-s390x-static go test を実行してもエラーが発生してしまうため go test を直接実行する手法は断念 しました。

$ qemu-s390x-static go test
Illegal instruction (core dumped)

テストの実行をコンパイルする

Go のテストは -c オプションをつけることで、テストを実行するバイナリをコンパイルします。また、-o をつけることでテストバイナリ名を指定できます。

$ GOOS=linux GOARCH=mips64 go test -c -o bitset.test

この手法を使い、コンパイルされたテスト実行バイナリを qemu を使って実行することにしました。

Go 言語では以下の CPU アーキテクチャに対応したバイナリをクロスコンパイルできます。

# go tool dist list | grep linux
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/s390x

これらのうちビッグエンディアンアーキテクチャppc64, mips, mips64, s390x です。(https://github.com/tsuna/endian/blob/master/big.go#L6

今回は、ppc64 アーキテクチャで正しく動くかをテストすることにしました。

以下の .travis.yml は、Go 言語 1.11 1.10 1.9 の3つのバージョンで、それぞれリトルエンディアン(amd64)とビッグエンディアンppc64)で正しい挙動をすることをテストしています。

language: go

os:
  - linux

before_install:
  - wget https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-ppc64-static
  - chmod 755 qemu-ppc64-static

go:
  - "1.11"
  - "1.10"
  - "1.9"

script:
  - GOOS=linux GOARCH=ppc64 go test -c -o bitset.test
  - go test ./... -v
  - ./qemu-ppc64-static ./bitset.test -test.v

これによって、リトルエンディアンとビッグエンディアンの両方でテストすることが可能になりました。

qemu-user-static のインストール

今回は、multiarch/qemu-user-static というリポジトリにアップロードされている qemu-ppc64-static を利用しましたが、qemu-user-static 自体は、apt-get でインストールできるので、そちらでインストールすることが望ましいです。

$ sudo apt-get update
$ sudo apt-get install -y qemu-user-static

しかし apt-get でインストールした qemu-user-static ではテストが途中で落ちる謎のバグがあるため、apt-get でのインストールは現在も格闘中です。

github.com

最後に

何とかビッグエンディアンとリトルエンディアンでテストを行うことができました。よかったです。

これの試行錯誤の過程は github.com/kawasin73/bitset リポジトリの issue に記録しています。

github.com

github.com