ハトネコエ Web がくしゅうちょう

プログラミングとかAndroid

GitHub Pages に、ビルドしたフォルダだけを上げたいときの Circle.yml

GitHub に git push するだけでウェブサイトを作成・更新できる GitHub Pages 。便利です。
ちょっとめんどくさいのが、 gh-pages ブランチか master ブランチのトップディレクトリに index.html がないといけないところ。
( master ブランチの場合は docs と名付けたフォルダをトップディレクトリにすることも可能)

gulp とか webpack とかで最終的な出力を build フォルダに向けてしている……なんて場合は、この仕様がけっこう厄介です。
デプロイ先のフォルダ名を docs とするのは一つの解決方法ですが、最終出力先のフォルダ名としてどうなの? って気持ちになります。

そこで、GitHub ユーザーにとっての CIツールとしておなじみ、 CircleCI の出番です!
CircleCIgh-pages ブランチへの git push をお願いしちゃいましょう!

0. 前置きの雑談

さっきから出力とかビルドとかデプロイとか言葉が安定しませんが、全部同じ意味で使ってると思ってください。
Sass が CSS ファイルになったり webpack で JS が出力されるのとかをコンパイルって呼んで、minify 系は圧縮と呼んで、それら一連のことがおこなわれるのをビルドとかデプロイとか呼んでる感じです。

正確にはそういう一連のコンパイルの流れを「ビルド」と呼んで、
人がさわれる状態になる(今回CircleCIが担当する、GitHub Pages への公開・更新)のを「デプロイ」と呼び、
サービス開始を「リリース」と言うらしいです。

私はビルドのことデプロイって言ったり、デプロイのことリリースって言ったりするので、
「あ〜この人あたまがわるいんだ〜」と笑いながら読んでください。

ディレクトフォルダも同じ意味くらいの気持ちで使ってますけど気にしないでね!

1. circle.yml の設定

答えから見せたほうがわかりやすいと思うので、まず出来上がりを見せます。
細かい注意点はそのあとに書いていきます。

src ディレクトリの中にある .scss ファイルや .pug ファイルを、タスクランナー gulp によってビルドし、
build ディレクトリに出力している形です。

1-1. CircleCI を働かさない branch の指定

general:
  branches:
    ignore:
      - gh-pages

まずはこの部分で、 gh-pages ブランチへの push があっても CircleCI が走らないよう設定されています。
今回は gh-pages ブランチに直接 push する予定はないので、この設定(もしくは以下に述べる [ci skip] の設定のいずれか)は本当はいりません。

f:id:nekonenene:20161102022958p:plain

上の写真のように、CI の実行がスキップされたログは残ります。
すこし邪魔ですが、ログがないと CircleCI の挙動がおかしいのか SKIPPED されたかわかりませんから必要ですね。

1-2. npm パッケージのインストー

dependencies:
  pre:
    - rm -rf node_modules
  override:
    - npm install

まず node_modules フォルダを削除しています。
CircleCI はキャッシュを持つのですが、 node_modules ディレクトリ内のキャッシュが悪さして変なところで CI が止まるということはよくあって、
しばらく原因解明に悩む時間がたびたび発生するので潔く削除しています。
(CircleCI には「Rebuild without cache」というボタンもあるのですが、そもそもキャッシュが原因の可能性を見落としがち)

1-3. ビルド

deployment:
  deploy:
    branch: master
    commands:
      - npm run build

branch: master と指定することで、 master ブランチへの push による CircleCI タスクのときのみ、この deploy 部分は働きます。
他のブランチへの push でも gh-pages に反映させたいという場合は、この記述は無くすといいでしょう。

その後に、 npm run build でビルドします。
以下のように package.json に書かれているために、npm run build とコマンドすると gulp build がおこなわれます。

# package.json より一部抜粋
  "scripts": {
    "build": "gulp build"
  }

gulp build の中身は gulpfile.js に記述しています。
大ざっぱに言うと、src ディレクトリにあるファイルをビルドして build ディレクトリに出力する処理が書かれています。

1-4. build ディレクトリの中身をトップディレクトリに

    commands:
      - npm run build
      - rm -rf src
      - cp -Rf build/. ./

ビルドし終わったら src フォルダは用済みなので消しています。
特に深い意味はありません。なんとなく消しています。

大事なのは cp -Rf build/. ./ の方です。
build フォルダの中身をトップディレクトリにコピーしてきます。

R オプションでフォルダの下を潜ってファイルを探します。
f オプションで、コピー先に同名のファイルがたとえあっても容赦なく上書きします。

個人的にハマったのは、build でも build/ でもなく、build/. が正解なことです。そっかー。

.gitignore によって build ディレクトリを含めてないから考えていませんでしたが、
ここ cp コマンドでなく mv コマンドでもよさそうですね。

1-5. git push で gh-pages へデプロイ

  - git config --global user.name  "CircleCI"
  - git config --global user.email "circleci@gh-pages.com"
  - git add .
  - git commit -m "Publish [ci skip]"
  - git checkout -B gh-pages
  - git push -u origin gh-pages --force

さて、ビルドが終わりました。
あとはデプロイ! git push までの流れを見ましょう。

1-5-1. git config

まずは git config で username と email の設定を入れます。
値はなんでもいいですが、この設定をしておかないとコミット git commit ができません。

1-5-2. git commit

コミットメッセージが「Publish [ci skip]」となっていますね。
この [ci skip] の部分が特別で、コミットメッセージにこの文字が含まれているコミットに対して、CircleCI は処理をおこないません。

通常は 1-1. で書いた特定のブランチを無視する方法だけでいいと思いますが、
おもしろい仕組みだなと思いましたので書いておきました。

1-5-3. add branch & checkout

続いて git checkout -B gh-pages です。

新しいブランチを作りつつチェックアウトする b オプションは知っていたのですが、
すでに同名のブランチがある場合はエラーを発生させてしまうオプションなので「どうしよう」と悩んでいたのですが、
この B オプションならば、基本は b オプションと同様ながら「すでに同名のブランチがある場合はブランチの作成をせずチェックアウトする」ようです。助かりました。

1-5-4. git push

そして最後に gh-pages ブランチをリモートリポジトリに git push します。
pull を要求されてはエラーになりますので、force オプションを付けます

2. git push だ! の前に……

CircleCI が git push できるようにするためには下準備が必要です。

CircleCI のプロジェクトページ(対象リポジトリ)に飛び、PROJECT SETTINGS を開き、
PERMISSIONS の「Checkout SSH keys」をクリックします。

f:id:nekonenene:20161102031951p:plain

すでに deploy key はあるかと思いますが、これには git pull の方の権限しかありませんので、user key を作成します。

Add user key のところにある「Authorize with GitHub」ボタンをクリックして user key を発行してください。
それが終わりますと下準備は完了です。

あとは push し無事にビルドが終了すれば、

https://YOUR_USENAME.github.io/REPOSITORY_NAME  

にページが作成されます。

3. Failed にならないために

test:
  override:
    - npm run test

CircleCI には test の部分があります。
ここを省略することは可能ですが、書かないと、必ず結果が Failed になってしまいます。

精神的に Failed ラベルが並ぶのは嫌ですし、
すべてが Failed となってしまうのでは、ビルドに失敗しての Failed などと見分けがつきません。

とはいえ、作りたてのプロジェクトなど、
テストコードがない場合もあるかと思います。

その場合は、test の部分は cd ./echo 'にんじんたべたい' などと、
当たり障りのないコマンドを書いておくといいかと思います。

4. 余談 : yarn

公開した circle.yml では npm install としていますが、実際は

dependencies:
  pre:
    - rm -rf node_modules
    - npm install -g yarn
  override:
    - yarn install

test:
  override:
    - yarn test

deployment:
  deploy:
    branch: master
    commands:
      - yarn build

のように、npm から yarn に乗り換えつつあります。
rm -rf node_modules のあとに yarn cache clear を書いたほうが安全かも)

今のところは特に問題が起きていません。
……が、yarn をインストールさせることなく npm だけで終わらせたほうが CircleCI のタスクにかける時間が 1分近く少なくて、複雑な気持ちになっています。
yarn.lock による安定さが優れてるのではって勝手に期待してるんだけど、どうでしょう?

gibo はときどき update しようなって話

便利ツール gibo

.gitignore を生成するのってめんどくさい、なんのファイル書けばいいねん。
ってなる悩みを解消してくれた gibo 。( https://github.com/simonwhitaker/gibo )

gibo OSX Windows Node >> .gitignore

# .gitignore がすでにある場合は >>
# 新規作成なら >> のとこは >

こんなコマンドで必要な .gitignore をバーっと書き込んでくれます。親切。

2015年12月から更新されてないですが、まあ単純な仕組みなのでいいのかな。

仕組みはシンプルで、
https://github.com/github/gitignore
リポジトリをユーザーディレクトリ直下に .gitignore-boilerplates というフォルダを作り clone してきて、
必要に応じてその中身のテキストを貼り付ける、という仕組みです。

というわけで大事なのが、 .gitignore-boilerplates の中身!

gibo のアップデート、ちゃんとしてますか?

gibo が最新版だから大丈夫、ではないのです。
大事なのは.gitignore-boilerplates の中身

これを更新してあげないことには .gitignore の中身が少しずつ現在のアプリに則さない、使えないものになってしまいます。

ってことで以下のコマンドです。

gibo -u

単純!

gibo -u を実行すると、以下のようなログが流れました。

Updating a55134f..0f88fa7
Fast-forward
 .github/PULL_REQUEST_TEMPLATE.md          |  11 +++++++
 Actionscript.gitignore                    |   8 +++++
 Android.gitignore                         |  15 ++++++++--
 Autotools.gitignore                       |   4 +++
 C++.gitignore                             |   4 +++
 C.gitignore                               |  19 ++++++++++++
 CMake.gitignore                           |   1 +
 CakePHP.gitignore                         |  14 ++++++++-
 Composer.gitignore                        |   2 +-
 Concrete5.gitignore                       |   1 +
 D.gitignore                               |   4 +++
 Delphi.gitignore                          |   9 ++++++
 Drupal.gitignore                          |   2 +-
 Eagle.gitignore                           |   8 +++++
 Elm.gitignore                             |   3 +-
 Erlang.gitignore                          |   2 +-
 ExtJs.gitignore                           |   8 +++++
 FuelPHP.gitignore                         |  23 ++++++++++++--
 Global/Ansible.gitignore                  |   1 +
 Global/Bazaar.gitignore                   |   2 ++
 Global/CVS.gitignore                      |   2 +-
 Global/Calabash.gitignore                 |  10 +++++++
 Global/Dreamweaver.gitignore              |   4 +++
 Global/Dropbox.gitignore                  |   4 +++
 Global/Eclipse.gitignore                  |  20 ++++++++++---
 Global/EiffelStudio.gitignore             |   2 +-
 Global/Emacs.gitignore                    |  10 +++++++
 Global/JetBrains.gitignore                |  31 ++++++++-----------
 Global/Linux.gitignore                    |   6 ++++
 Global/Matlab.gitignore                   |   3 ++
 Global/MicrosoftOffice.gitignore          |   3 ++
 Global/NetBeans.gitignore                 |   2 --
 Global/SublimeText.gitignore              |  13 ++++++++
 Global/Tags.gitignore                     |   1 +
 Global/Vim.gitignore                      |   6 +++-
 Global/VirtualEnv.gitignore               |   2 ++
 Global/VisualStudioCode.gitignore         |   6 ++--
 Global/Xcode.gitignore                    |   8 ++---
 Global/XilinxISE.gitignore                |  10 +++++++
 Global/{OSX.gitignore => macOS.gitignore} |  50 ++++++++++++++++---------------
 Go.gitignore                              |   6 ++++
 Gradle.gitignore                          |   5 +++-
 Haskell.gitignore                         |   4 +++
 Joomla.gitignore                          |  37 +++++++++++++++++++++++
 Julia.gitignore                           |   4 +++
 KiCAD.gitignore => KiCad.gitignore        |   9 +++++-
 LICENSE                                   | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 Laravel.gitignore                         |   6 ++--
 Leiningen.gitignore                       |   3 +-
 Magento.gitignore                         |  40 ++++++-------------------
 Nanoc.gitignore                           |   4 +--
 Node.gitignore                            |  20 +++++++++++--
 Objective-C.gitignore                     |  19 +++++++++---
 OpenCart.gitignore                        |   9 ++++++
 PlayFramework.gitignore                   |   1 -
 Prestashop.gitignore                      |   9 +++++-
 Python.gitignore                          |  31 +++++++++++++++++++
 Qt.gitignore                              |   8 +++--
 R.gitignore                               |  14 +++++++++
 README.md                                 |   2 +-
 Rails.gitignore                           |  17 ++++++++---
 Ruby.gitignore                            |  16 +++++++++-
 Rust.gitignore                            |  17 +++++------
 Scala.gitignore                           |   4 +++
 Scheme.gitignore                          |   7 +++++
 Smalltalk.gitignore                       |  18 +++++++++++
 SugarCRM.gitignore                        |   2 ++
 Swift.gitignore                           |  24 +++++++++++----
 Symfony.gitignore                         |  13 +++++++-
 TeX.gitignore                             |  68 ++++++++++++++++++++++++++++++++++++++----
 Terraform.gitignore                       |   6 ++++
 Typo3.gitignore                           |   2 +-
 Umbraco.gitignore                         |   8 +++--
 Unity.gitignore                           |  12 +++++++-
 UnrealEngine.gitignore                    |   7 +++--
 VisualStudio.gitignore                    |  58 +++++++++++++++++++++++++++++++-----
 WordPress.gitignore                       |   8 ++---
 ZendFramework.gitignore                   |   5 ++++
 78 files changed, 812 insertions(+), 180 deletions(-)
 create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
 create mode 100644 Global/Ansible.gitignore
 create mode 100644 Global/Bazaar.gitignore
 create mode 100644 Global/Calabash.gitignore
 create mode 100644 Global/Dropbox.gitignore
 rename Global/{OSX.gitignore => macOS.gitignore} (81%)
 create mode 100644 Julia.gitignore
 rename KiCAD.gitignore => KiCad.gitignore (56%)
 create mode 100644 Scheme.gitignore
 create mode 100644 Smalltalk.gitignore
 create mode 100644 Terraform.gitignore

死ぬほど変わってた・・・。

a55134f のリビジョンっていつなのかと見たら 2015/10/30 でした。
つまり私が gibo を使って一周年ということですね?!

あと注目すべきは

rename Global/{OSX.gitignore => macOS.gitignore}

の行です。

冒頭で書いたコマンドは今後は

gibo macOS Windows Node >> .gitignore

と書かなければいけません。ちょっとタイプ量が増えますが、macOSって名前にもなじんできたのですぐに慣れるはず。

以上、 gibo -u 大事だよ!
って話でした。「gibo OSX」と打ち込んでいる人を見かけたら、口汚く罵ってあげてください。

macOS Sierra で mysql2 の bundle install に失敗する場合

Rails のプロジェクトを扱っていて、
gem を bundle install していたところ mysql2 の部分でこんなエラーが…

Installing mysql2 0.4.4 with native extensions

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/gems/mysql2-0.4.4/ext/mysql2
/Users/user_name/.rbenv/versions/2.3.1/bin/ruby -r ./siteconf20161031-6252-1pfzkzr.rb extconf.rb
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for rb_thread_blocking_region()... no
checking for rb_wait_for_single_fd()... yes
checking for rb_hash_dup()... yes
checking for rb_intern3()... yes
-----
Using mysql_config at /usr/local/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for mysqld_error.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/Cellar/mysql/5.7.16/lib
-----
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/mysql2-0.4.4/mkmf.log

current directory: /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/gems/mysql2-0.4.4/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/gems/mysql2-0.4.4/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/gems/mysql2-0.4.4 for inspection.
Results logged to /Users/user_name/Programs/web/test-repo/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/mysql2-0.4.4/gem_make.out

An error occurred while installing mysql2 (0.4.4), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.4.4'` succeeds before bundling.

で、 gem install mysql2 -v '0.4.4' を試したらそれは正常に実行できるので困った。

エラーメッセージの途中に library not found for -lssl と書かれていることから
openssl のライブラリ周りで問題が起きているのはわかるのですが、openssl を再インストールしたところで状況は変わらず
なかなか困っていました。

解決法1

ここの記事が解決してくれました : RailsプロジェクトでMySQLがbundle installできなかった - Qiita

この記事に書かれているコマンドと少し異なるのですが、

bundle config build.mysql2 --with-opt-lib=/usr/local/opt/openssl/lib --with-opt-include=-I/usr/local/opt/openssl/include

で解消できました。

なお、bundler のグルーバル設定を変えたくない場合は --local のオプションを上記コマンドに付けるといいです。

解決法2(こっちのが良さそう)

違うやり方として、この記事 : Pry起動時にエラーが出る場合の対処法 - Qiita
を参考に、

CONFIGURE_OPTS="--with-readline-dir=/usr/local/opt --with-openssl-dir=/usr/local/opt" rbenv install 2.3.1

と、openssl のディレクトリを明示した上で Rubyコンパイルをおこなった上で…という方法でもいけました。

解決法3(追記:2017/1/25)

コメントでいただきました。
現在は xcode-select --install でも対応できるようです。

最新の Xcode Command Line Tools では、この問題に対応しているんでしょうね。
というわけで、App StoreXcode の最新版をインストールしたのち

xcode-select --install

を実行してみてください。
(追記ここまで)


ついでに nokogiri も gem install に失敗してたけど、そちらはこの記事で助かりました。 : OSX への nokogiri 1.6.8.rc3 の install でハマったメモ - Qiita

たしかに以前 brew doctor で指摘されてるから unlink した覚えがある。
ってことで、

brew link --force libxml2

したあとに gem install nokogiri -- --use-system-libraries でインストール可能。
bundle config の方は以下のように設定。

bundle config build.nokogiri --use-system-libraries

これで mysql2 や nokogiri が Gemfile に含まれていても bundle install に成功するようになりました。
めでたしめでたし

npm と yarn のインストール時間を比較してみた

彗星の如く現れた yarn
Facebook社の中の人が作った yarn は、 npm を発展させたパッケージ管理ツールです。*1

npm 同様 package.json を読み込み、npm リポジトリとほぼ同様の yarn リポジトリからダウンロードしてくるものなので、
npm からの移行は簡単です。

まずは

brew install yarn

もしくは

npm install -g yarn

にて yarn をインストールしてきて、( npm に取って代わるものとして作られているため、前者のやり方を yarn としては推奨しているようだ)
それから現在の node_modules フォルダを削除し、

rm -rf node_modules

yarn install を実行するだけです。( yarn install とコマンドしても yarn とコマンドしても同じ )

yarn

さて、気になるのは yarn install にかかる時間。
本当に速くなっているのでしょうか?
time コマンドを利用し見てみましょう。

今回インストールするパッケージの一覧は以下のとおりです。

# package.json より抜粋
  "dependencies": {
    "font-awesome": "^4.7.0",
    "zepto": "^1.2.0"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.18.0",
    "csso": "^2.3.0",
    "del": "^2.2.2",
    "gulp": "^3.9.1",
    "gulp-babel": "^6.1.2",
    "gulp-htmlmin": "^3.0.0",
    "gulp-imagemin": "^3.1.0",
    "gulp-postcss": "^6.2.0",
    "gulp-pug": "^3.1.0",
    "gulp-replace": "^0.5.4",
    "gulp-sass": "^2.3.2",
    "gulp-sourcemaps": "^2.2.0",
    "gulp-uglify": "^2.0.0",
    "gulp-webserver": "^0.9.1",
    "postcss-cssnext": "^2.8.0",
    "postcss-csso": "^1.1.2",
    "pump": "^1.0.1"
  }

また、
npm のバージョンは v3.10.8
yarn のバージョンは v0.16.1 を使用しています。

time npm install

# 中略

npm install  35.38s user 10.38s system 51% cpu 1:29.60 total

約1分30秒かかりました。続いて yarn です。

time yarn

yarn install v0.16.1
info No lockfile found.
[1/4] ?  Resolving packages...
warning gulp > vinyl-fs > glob-stream > minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
warning gulp > vinyl-fs > glob-watcher > gaze > globule > minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
warning gulp > vinyl-fs > glob-watcher > gaze > globule > glob > graceful-fs@1.2.3: graceful-fs v3.0.0 and before will fail on node releases >= v7.0. Please update to graceful-fs@^4.0.0 as soon as possible. Use 'npm ls graceful-fs' to find it in the tree.
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
success Saved lockfile.
✨  Done in 80.57s.
yarn  25.27s user 13.88s system 48% cpu 1:21.12 total

約1分20秒です。少し速いですね。
それから、行数のすごく長い npm install と違い、絵文字を使いつつシンプルに出力されているのが見て取れますね。

そしてここからが yarn の本領発揮です。

yarn は一度インストールしたものはキャッシュに貯め、
次回インストール時はキャッシュにあるものを使い、リポジトリからダウンロードしません。

それにより、インストール時間を大幅に削減しているのです。
再びインストールしてみましょう。

rm -rf node_modules
time yarn
yarn install v0.16.1
[1/4] ?  Resolving packages...
[2/4] ?  Fetching packages...
[3/4] ?  Linking dependencies...
[4/4] ?  Building fresh packages...
success Saved lockfile.
✨  Done in 23.81s.
yarn  13.32s user 8.05s system 88% cpu 24.253 total

約24秒です!
従来の npm install と1分以上の違いがあります。

また、キャッシュにあるものを持ってきているだけなので、
ネットにつなげていない状況でも yarn コマンドは上のとおりに動作します。

まあ、問題は……

du -sh ~/.yarn-cache
 86M    /Users/user_name/.yarn-cache

ユーザーディレクトリに作られる yarn のキャッシュフォルダの容量がどんどんと大きくなっていきそうなのが心配ですね……。