tofucodes diary

にほんごのほう

Travis CIでCarthageのビルドをキャッシュして幸せになる

目的

  • Travis CIのPull RequestビルドでCarthageを毎回ビルドしない
  • git repositoryにCarthageのビルド成果物をコミットしないで実現する

Pull Requestビルドの概要

公式ドキュメントを覗いてみます。

Caching Dependencies and Directories - Travis CI

Pull Requestのビルドは以下のキャッシュを確認して最初に見つかったものを利用する。

  1. 対象のPull Requestのキャッシュ
  2. 対象のPull Requestのターゲットブランチのキャッシュ
  3. git repositoryのデフォルトブランチのキャッシュ

もし上記すべてのキャッシュが存在しない場合はキャッシュなしでビルドが実行される。

最初のPull Requestビルドが終了した後に、新しくPull Requestキャッシュが作成される。(つまり上記リストの1.はPull Requestの初回ビルドでは存在しない)

やってみる

  • ターゲットブランチ: develop(ブランチキャッシュはすでに存在)
  • デフォルトブランチ: master

.travis.yml

cache:
  directories:
    - Carthage

featureブランチ -> develop へPRを作成します。 キャッシュが効いていたらCarthageのビルドが走らないため6分程度で終わるはずです。

f:id:tofucodes:20180615220107p:plain

...時間がかかり過ぎています。

原因を特定するためにTravisのログを見てみます。

Setting up build cache
$ export CASHER_DIR=$HOME/.casher
$ Installing caching utilities

attempting to download cache archive
fetching PR.118/cache-osx-xcode9.3-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--rvm-default--gemfile-Gemfile.tgz
fetching PR.118/cache--rvm-default--gemfile-Gemfile.tgz
fetching master/cache-osx-xcode9.3-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--rvm-default--gemfile-Gemfile.tgz
fetching master/cache--rvm-default--gemfile-Gemfile.tgz

could not download cache

fetching... の内容がキャッシュの存在確認のようですが、Pull Requestキャッシュとmasterブランチキャッシュしか確認してないように見えます。

公式ドキュメントの内容が正だとしたらTravis CIのバグのような気がしますが、とりあえずdevelopブランチをデフォルトブランチに設定して別のPRを試してみます。(同じPRだとすでにPull Requestキャッシュが作成されてしまって検証にならないため)

f:id:tofucodes:20180615220058p:plain

17分から6分に短縮することができました。

Travisのログを見てみてもdevelopブランチのキャッシュを確認していることが分かります。

Setting up build cache
$ export CASHER_DIR=$HOME/.casher
$ Installing caching utilities

attempting to download cache archive
fetching PR.124/cache-osx-xcode9.3-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--rvm-default--gemfile-Gemfile.tgz
fetching PR.124/cache--rvm-default--gemfile-Gemfile.tgz
fetching develop/cache-osx-xcode9.3-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--rvm-default--gemfile-Gemfile.tgz

found cache

結論

  • Travis CIのPull Requestビルドはターゲットブランチのブランチキャッシュを確認してくれない
  • 公式ドキュメントが正だとしたら上記の挙動はTravis CIのバグの可能性
  • git repositoryのデフォルトブランチをうまく設定することで、初回ビルドからTravis CIのキャッシュ機構の恩恵を受けることができる(今回の例ではdevelopを利用しましたがmasterでもビルドするような環境の場合はmasterでも大丈夫なはず)

また以前別エントリで記載したように、CI時のビルド時間短縮の観点から、Carthageを利用するアプリでCarthageフォルダをgit repositoryにコミットするか否か迷っていましたが、Travis CIを利用する場合はキャッシュ機構が備わっているためgit repositoryにコミットしなくて済みrepositoryをクリーンに保てるので良いですね。

tofucodes.hatenablog.jp

指定期間のデータを抽出するアンチパターンについて

何かしらのデータのリストから特定の期間のデータのみをフィルタしたいような時があると思います。

今回のサンプルはデータのリストから6月に作成されたデータをフィルタします。

おそらくよくありがちなコードはこんな感じではないでしょうか。

(コードはだいぶデフォルメしてます)

let dataList: [SomeData] = ...
// 2018/06/01 00:00:00 <= x <= 2018/06/30 23:59:59
let filtered = dataList.filter { Date("2018-06-01 00:00:00") <= $0.createdAt && $0.createdAt <= Date("2018-06-30 23:59:59") }

弊社のiOSアプリにもこのようなコードが存在していました(そしてバグがありました)。

これはあまり良くないです。

なぜこれが良くないかというと、比較の後ろのデータが無限に取りうるから(23:59:59.999.... <<< 越えられない壁 <<< 00:00:00)。

それを踏まえたより良い実装はこちら。

let dataList: [SomeData] = ...
// 2018/06/01 00:00:00 <= x < 2018/07/01 00:00:00
let filtered = dataList.filter { Date("2018-06-01 00:00:00") <= $0.createdAt && $0.createdAt < Date("2018-07-01 00:00:00") }

実際にSwiftのDateオブジェクトの比較でどこまで細かい秒数まで使われるか知らないので、もしかしたら問題ないかもしれないですが、

この考え方自体は、些細なことですが言語にかかわらず結構重要な気がします。

Carthageの導入で困った7つのこと

先日仕事で初めてCarthageを利用した際に困った点やつまづいた点。

ラインナップ

  1. Carthage関連ファイルどこまでcommitするか問題
  2. carthageのコマンド多くてどれ使えば良いかよく分からない問題
  3. Fabric/Crashlyticsが公式にサポートしてない問題
  4. Firebaseが公式にサポートしてない問題
  5. Firebase.frameworkがbitcodeサポートしてない問題
  6. App Installation Failed 問題
  7. binaryダウンロードのバージョン解決に不具合がありそう?問題

Carthage関連ファイルどこまでcommitするか問題

CartfileとCartfile.resolvedは必須として以下のファイル群をcommitするのかどうかという話。

  • Carthage/Checkouts
  • Carthage/Build

qiita.com

世間には色々な意見があるようだけど、私個人としては以下を理由としてCheckouts, Build共にcommitしない方針にしました。

  • Cartfile.resolvedがあれば原則的には同じ開発環境を作れる
  • Githubに不要なDiffが出るのが好きじゃない
  • 現状はCIをしていないのでビルド時間にそこまでシビアになる必要がない

仕事ではチームで開発を行ってるため上記のような方針にしましたが、個人開発とかでGithubのPull Request運用じゃない環境だったら2番目の理由は当てはまらなくなるんで、好きなファイルcommitしてしまえば良いんじゃないんですかね。

carthageのコマンド多くてどれ使えば良いかよく分からない問題

$ carthage
Available commands:

   archive           Archives built frameworks into a zip that Carthage can use
   bootstrap         Check out and build the project's dependencies
   build             Build the project's dependencies
   checkout          Check out the project's dependencies
   copy-frameworks   In a Run Script build phase, copies each framework specified by a SCRIPT_INPUT_FILE environment variable into the built app bundle
   fetch             Clones or fetches a Git repository ahead of time
   help              Display general or command-specific help
   outdated          Check for compatible updates to the project's dependencies
   update            Update and rebuild the project's dependencies
   version           Display the current version of Carthage
  • archive = frameworkを提供するときに必要そうな感じ
  • bootstrap = build + checkoutな感じだけどそれぞれがよく分からない
  • build = frameworkをビルドするコマンド?
  • checkout = ?
  • fetch = ??
  • update = ライブラリのバージョンアップしたい時に必要そうな感じ

自分の最初の印象としてはこんな感じでした。 しかもそれぞれのコマンドにさらにたくさんのオプションまで用意されててもう何が何だか....

結論、簡単に使うなら以下くらいで大丈夫な気がしてます。

# Carthage導入時
$ carthage update --platoform iOS

# Carthage導入済みのプロジェクトをcloneしてきて依存ライブラリをビルドする最初のステップ
# Cartfile.resolvedの内容を元にビルドされます
$ carthage bootstrap --platform iOS

# (全ての)依存ライブラリをバージョンアップする時
$ carthage update --platform iOS --cache-builds

# 特定の依存ライブラリをバージョンアップする時
$ carthage update "$LIBRARY_NAME" --platform iOS

FYI: 各コマンドのオプションの詳細は以下のコマンドで見れる

# carthage help <command name>
$ carthage help bootstrap

Fabric/Crashlyticsが公式にサポートしてない問題

FabricとCrashlyticsはCocoaPodsはサポートしてるけどCarthageはサポートされてないので代替手段を検討する必要があります。

github.com

Carthageのバイナリ形式ダウンロードで必要になるjsonファイルを親切な人がメンテしてくれてるので、私はこちらをありがたく利用させてもらいました。

binary "https://raw.githubusercontent.com/Building42/Specs/master/Carthage/Fabric.json"
binary "https://raw.githubusercontent.com/Building42/Specs/master/Carthage/Crashlytics.json"

FYI: メンテされてるレポジトリはここ

github.com

Firebaseが公式にサポートしてない問題

Firebaseも同じく公式にはまだサポートされてないのですが、こちらはバイナリダウンロード用のjsonファイルを公式が試験的にメンテしてくれているようです。フィードバック次第では今後Carthageを公式にサポートする可能性もあるようです。

github.com

binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json"
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAuthBinary.json"
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDatabaseBinary.json"
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseDynamicLinksBinary.json"
binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseMessagingBinary.json"

Firebase.frameworkがbitcodeサポートしてない問題

前述のようなCartfileでダウンロードを行うとFirebase.frameworkというframeworkがダウンロードされるので何も考えずにアプリにリンクしたところbitcodeがサポートされてないというエラーになりました。

$ otool -l Carthage/Build/iOS/Firebase.framework/Firebase | grep __LLVM
# 何も出力されない

対応の結論としては、Firebase.framework自体の中身と、CocoaPods時代にこのframeworkをリンクしていなかったことから判断して、Firebase.frameworkはアプリにリンクしませんでした。

App Installation Failed 問題

全ての依存ライブラリをCarthageに移行していざアプリをRunしようとするとエラーが...

f:id:tofucodes:20180421175259p:plain

This application or a bundle it contains has the same bundle identifier as this application or another bundle that it contains. Bundle identifiers must be unique.

原因はFirebaseのframeworkをCarthageのcopy-frameworksの対象にしてたことでした。Firebaseのframeworkはstatic libraryになっており、static libraryはcopy-frameworksの対象にできないようです。以下参照。

github.com

binaryダウンロードのバージョン解決に不具合がありそう?問題

Carthageに移行前CocoaPodsで4.9.0のFirebase SDKを利用していたので、Carthageでも以下のようなバージョン指定でダウンロードを試みました。私の認識が正しければ、以下のようなバージョン指定では4.9.Xの最新版をダウンロードしてくれるものかと思ってました。

binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json" ~> 4.9.0

しかし実際のところcarthage updateを実行するとダウンロードされたバイナリは4.12.0(4.Xの最新版)でした。これがCarthageのバグなのか私の認識違いなのかまだ不明なので分かる方がいたら教えてほしいです。

もし4.9.0をダウンロードしたい場合は以下のように厳密に指定してあげれば可能です。

binary "https://dl.google.com/dl/firebase/ios/carthage/FirebaseAnalyticsBinary.json" == 4.9.0

上記の挙動については私の認識違いでした。 CarthageのREADMEをよく見ろと言う話でした。

github.com

Compatibility is determined according to Semantic Versioning. This means that any version greater than or equal to 1.5.1, but less than 2.0, will be considered “compatible” with 1.5.1.

互換性の定義は Semantic Versioning の定義に従い、~> 1.5.1 は 1.5.1以上2.0未満となる。

According to SemVer, any 0.x.y release may completely break the exported API, so it's not safe to consider them compatible with one another. Only patch versions are compatible under 0.x, meaning 0.1.1 is compatible with 0.1.2, but not 0.2. This isn't according to the SemVer spec but keeps ~> useful for 0.x.y versions.

Semantic Versioning によると、0.x.y の場合はすべて互換性が無いものと考える。(しかしCarthageは?)パッチバージョンアップを互換性ありと考えるようにする(0.1.1 -> 0.1.2は互換性あり、0.1.2 -> 0.2 は互換性なし)。これはSemantic Versioningに則ってないけど、0.x.yのバージョンの場合は便利

つまりFirebaseのライブラリを~> 4.9.0と指定しているにも関わらず4.12.0がダウンロードされていたのは正常な動作でした。

続報:iOS 11からNSLocaleの仕様が変更されたらしい

以前iOS 11のNSLocaleについて書いた記事はこちら。

10月にAppleのDeveloper Forumに投稿していた質問を久しぶりに見てみたら2月に返信がありました。

翻訳しながら内容を見てみます。


I think the change in behavior makes sense sort of for many things, but would be nice if there was a way to opt out and get the real device locale even if your app doesn't support the user's native language.

  • 今回の変更はまあいろんな意味で理にかなってると思うけど
  • アプリがその言語をサポートしてなくてもユーザのデバイスの実際のロケールを取得できるような方法があったら良かったね

For example something like this:

  • 例えばこんな感じで
+(NSLocale*)deviceLocale; //Device locale, even if the app is not localized in the given language. 

My app displays dates and I've had a couple users complain that they want the date formatted in the style of their language. Even though my app isn't localized in their language, they don't care, they want the date the way they want it.

Does anyone know of a way to get the device locale like +currentLocale used to?

  • 私のアプリは日付を表示してるけど、ユーザの言語のスタイルでフォーマットされた日付が欲しいと訴えてきたユーザが幾人かいました
  • たとえ私のアプリが彼らの言語にローカライズされていなくても、彼らはそんなこと気にせず、彼らの望むフォーマットの日付が欲しいのです
  • 以前+currentLocaleでできたように、デバイスの実際のロケールを取得する方法をどなたか知りませんか?

Edit to answer my own question, below seems to work:

  • 自分の質問に自分で回答します。以下の方法でうまくできそうです
NSString *localeID = NSLocale.preferredLanguages.firstObject;  
NSLocale *deviceLocale = [NSLocale localeWithLocaleIdentifier:localeID];  
//use the locale  

上記の方法はまだ実際に試していないので時間があったら試してみようと思いますが、今までcurrentLocaleを利用していたロジックを上記に置き換えることで、iOS 11以降でもアプリのローカライズに関係なく端末の言語設定に準じたロケールを取得することができそうです。

入社1日目のMac環境設定まとめ

メモエントリ

  1. キーボード設定変更
  2. Apple ID作成
  3. Xcodeインストール
  4. XVim2セットアップ
  5. Alfredインストール
  6. id_rsa作成
  7. ターミナルセットアップ

1. キーボード設定変更

System Preferences > Keyboard > Modifier Keys... からCaps Lockを⌃に変更する

2. Apple ID作成

会社のメールアドレスでApple IDを作成する

Developer Teamに招待してもらいジョインする

3. Xcodeインストール

App StoreからXcodeをインストールする

2.で作成したApple IDでログインする

4. XVim2セットアップ

5. Alfredインストール

App StoreからAlfredをインストールする

⌃+Spaceをキーバインドに設定する

6. id_rsa作成

Connecting to GitHub with SSH - User Documentation

RSAキーペアを作成する

$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

~/.ssh/configを作成する

Host *
 AddKeysToAgent yes
 UseKeychain yes
 IdentityFile ~/.ssh/id_rsa

キーチェーンにRSAキーを登録する

$ eval "$(ssh-agent -s)"
$ ssh-add -K ~/.ssh/id_rsa

Githubに公開鍵を登録する

$ pbcopy < ~/.ssh/id_rsa.pub
# コピーした鍵をGithubの設定ページに登録する

sshの接続確認

$ ssh -T git@github.com

7. ターミナルセットアップ

homebrewをインストールする

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

homebrew経由でtmuxをインストールする

$ brew install tmux

dotfilesをcloneしてセットアップする

$ git clone git@github.com:torufuruya/dotfiles.git
$ ln -s dotfiles/.tmux.conf ~/
$ ln -s dotfiles/.zshrc ~/

20代後半の冴えないエンジニアが転職活動でそれなりの成果を出すためにやったこと

これといって特徴がなく冴えないエンジニアの私ですが、自分の軸に合致した企業に年収アップで転職を決めることができたので、転職活動で考えてたこととか行ったことについて共有して、万に一つ少しでもどなたかの参考になったら本望なエントリです。

職探し

LinkedInとWantedlyを利用しました。前者は基本的にリクルータとの窓口で、後者は企業の採用担当者(スカウト)との窓口として利用しました。

Wantedlyは多いときは日に何通もスカウトが来て有頂天になることができましたが、自分の軸に合っていそうな企業からのスカウトにのみ反応しました。結果的には1社のみ採用フローに進み内定をいただくところまで行きました(諸事情により辞退しましたが)。

LinkedInも毎日毎日様々な企業のリクルータからメッセージとコンタクト申請が来ますが、私はどうせならと英語の練習をかねて日本人じゃないリクルータ4名ほどに転職活動をお手伝いしてもらいました。電話する時は毎回緊張したけど、英語を話す良い機会になったし今では良い思い出です。リクルータの数4名と言うのはちょっと多いかもしれないですが、メリットデメリットあるかなと思います。

メリット

  • 他社が知らないユニークな求人がもらえたりする(L社とかの大手の求人は大体どこもかぶる)
  • 英語を話す機会を増やせる(これは私個人に限る)

デメリット

  • 時間と手間がかかる
  • 面接日程の調整が難しい(内定承諾の期限ために他社の面接を辞退せざるを得なかった)

あと今回、リクルータ経由と直接企業へ応募するフローを両方とも経験しましたが、リクルータ経由の大きなメリットとして「不採用の理由を聞くことができる」というのがあることを知りました。直接応募の場合も会社によっては教えてくれるかもしれませんが、理由については公開できない的な記載がメールにあったりします。私が今回直接応募して不採用だった企業はどこも理由を教えてはくれませんでした。

職務経歴書

以前海外の企業に応募した際に英語のレジュメを作成していたので今回も英語のものを利用しました。日本語の経歴書も欲しいと言う企業があったのはダルかったですが、いまどきのイケてるIT企業なら外資系じゃなくても英語のレジュメを受け取ってくれるので大丈夫です。

上記のエントリを書いた時は正直良い結果が得られなかったので、今回は以下のサイトを参考にして以前のレジュメをブラッシュアップしました。

LifeClever ;-)Give your résumé a face lift

レジュメは内容が一番大事なのはもちろん言うまでもないんですが、上記のサイトではレジュメの「見た目」についての考察とアドバイスが述べられてます。「まずは」毎日多くのレジュメを見てる採用担当者の目に止まることが大事ということですね。こちらを元に作成したレジュメのテンプレがこちらです。

Resume Template - Google ドキュメント

あの誰もが知るグローバル大企業G社のレジュメスクリーニングを通過することができましたし、他にもこのレジュメでスクリーニングに落ちることはほぼなかったので、ある程度信ぴょう性のあるレジュメなんじゃないかなと思います。

試験・面接対策

大学でコンピュータサイエンスを専攻していなかったためまずは基本的な勉強から行うために本屋で吟味して以下の著書を購入しました。今回は転職活動の試験・面接対策が趣旨のため経営などのトピックはスキップして技術的な知識のみを勉強しました。既にCSの知識がある人は全く無用です。この本で基本を掴んだのちに次の著書でより試験対策にフォーカスしました。

データ構造とアルゴリズム問題の対策として利用したのは言わずと知れた?以下の著書。Cracking the Coding Interviewという米国ではおそらく相当有名な(ベイエリアのYoutuberの動画でよく見る)著書の日本語訳版です。2017年の年初に1社だけ筆記試験を受けた際にこの類の問題がボロボロだった経験から対策が必要と思いこの著書を購入して勉強しました。とても分厚い内容で前半の方しかやる事ができませんでしたが、その後筆記試験でもそれなりに回答する事ができるようになりました。

世界で闘うプログラミング力を鍛える本 ~コーディング面接189問とその解法~

世界で闘うプログラミング力を鍛える本 ~コーディング面接189問とその解法~

その他のTips

面接前はカフェインを摂取する

カフェインを摂取すると頭が良く回ることが科学的に?立証されてるらしいです。30分程度で効果が表れるみたいです。私も面談の前はコーヒーを飲んでから臨むようにしてました。

面接は可能ならば午前中に入れる

たまたまかもしれませんが午前中に受けた面接は総じて結果が良く、夜受けたものは良くありませんでした。仕事後だと疲れが顔に出るんでしょうか?w

一部上場の大手ソーシャルゲーム会社を退職しました。

この度、約6年間勤めた一部上場の大手ソーシャルゲーム会社を退職しました。

といってもこの会社で実際に働いたのはおよそ3年間で、後半の3年間は子会社に出向していました。

思い返せば2012年の新卒採用で未経験の私を採用していただき、様々なチームで色々なプロジェクトに携わらせていただきました。

まさにエンジニア人生はここから始まったという意味では、大変ありがたい6年間だったなと思います。

入社当時の飛ぶ鳥を落とす勢いの雰囲気を肌で感じる事ができたこと、また盛者必衰を内部の人間として見る事ができたことはとても貴重な経験でした。

両社ともに居心地が良く可能ならばずっと勤めたいくらいでしたが、

年齢ももう若くない頃になってしまったのでそろそろコンフォートゾーンから抜け出すべく転職を決めました。

転職活動の詳細については色々と書きたい事があるので別エントリーにしようかなと思います。