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

プログラミングやサーバー・Web制作、チームマネジメントなど得た技術のまとめ

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 をお願いしちゃいましょう!

【追記(2019/5/24)】
この記事の内容は古いです。現在は CircleCI 2.0 ですので、以下の記事をご覧ください。

nekonenene.hatenablog.com

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 による安定さが優れてるのではって勝手に期待してるんだけど、どうでしょう?