CircleCI - macOS イメージを使用するときに Ruby のバージョンを指定する
はじめに
CircleCI が用意している macOS イメージを使用する際に、任意のバージョンの Ruby を使いたい場合があると思います。この記事では、macOS イメージにプリインストールされている chruby を用いて、Ruby バージョンを切り替える方法を紹介します。
macOS Container の Ruby 環境について
既に複数バージョンの Ruby が用意されている
macOS が用意しているコンテナイメージには、コンテナがビルドされた時点で最新である安定版の Ruby と、その他にもいくつかのバージョンの Ruby が用意されています。各イメージのマニフェストを確認することで、それぞれのイメージで用意されている Ruby の一覧を確認できます。
例えば、サポートされている Xcode Versions 一覧 から 11.3.0 (Build 11C29) の Installed Software を確認すると、
が、それぞれ用意されていることが分かります。
使用するイメージによって chruby の Auto-Switching 機能の設定が違う
chruby には、各プロジェクトに配置された .ruby-version
を見て、Ruby のバージョンを自動で切り替えてくれる Auto-Switching 機能があります。
macOS 10.14 (Mojave) / Xcode 11.1 とそれ以前のイメージでは、この Auto-Switching 機能がデフォルトで有効に設定されていましたが、macOS 10.15 (Catalina) / Xcode 11.2 とそれ以降のイメージでは、自分で Auto-Switching 機能を有効に設定する必要があります。
なお、この仕様変更の理由が気になったので調べてみると、こちらのディスカッションで言及されていて、
The reason that I decided to leave it off by default, is that it’s easier for users who don’t expect it to understand the system. There is no “magic” happening in the default image. To enable autoswitching, you will have to add an explicit step to your build.
In my opinion (and I’m not claiming to be right), it’s easier to explain to someone that they need to enable the autoswitcher to select different versions of Ruby, than to explain to someone that they need to remove the autoswitcher from their bash profile to disable it.
要約すると、以下のような理由から変更したようです。
- Auto-Switching 機能をデフォルトで有効でなくした理由は、Auto-Switching することを期待していないユーザーにもよりシステムを理解してもらいやすい
- Auto-Switching しないように
~/.bash_profile
から Auto-Switching するコードを削除する必要があることを説明するよりも、有効にする方法を説明するほうが簡単
この記事では、macOS 10.15 (Catalina) / Xcode 11.2 とそれ以降のイメージを使用することを想定して説明します。
chruby を使用して任意のバージョンの Ruby を指定する
chruby の Auto-Switching 機能を有効にする
chruby の README.md を参考に Auto-Switching を有効にするコマンドを追加しました。
commands: fix_ruby_version: steps: - run: name: Auto-Switching ruby version with chruby command: | echo 'source /usr/local/share/chruby/chruby.sh' >> $BASH_ENV echo 'source /usr/local/share/chruby/auto.sh' >> $BASH_ENV source $BASH_ENV - run: name: Install Bundler gem command: gem install -N bundler
また、切り替えた先の Ruby に Bundler がインストールされていないことがあるので、このタイミングで一緒にインストールするようにしています。
Ruby バージョンを指定するための .ruby-version
を用意する
プロジェクトのルートディレクトリに、.ruby-version
ファイルを作成し、イメージで用意されている任意の Ruby のバージョンを記述します。
ruby-2.6.5
各ジョブからコマンドを呼び出す
各ジョブで Ruby を使用する(Ruby gems をインストールするなど)より前に fix_ruby_version
コマンドを呼び出すと良いと思います。
さいごに
ふとした時、なぜか .ruby-version
で指定したバージョンに切り替わっていないことに気づき、調べてみると macOS 10.15 (Catalina) / Xcode 11.2 以降は Auto-Switching 機能の設定が必要だったということが分かった、、、ということがあり、今回記事にしてみました。
ちなみに、CircleCI の日本語ドキュメントにはいまのところ仕様変更への言及はなかったので、まずは英語ドキュメントをあたってみる心構えが大事なのかも、という気付きもありました。
参考
CircleCI - iOSシミュレーター向け(x86_64)にアーカイブをビルドする
はじめに
CircleCI 上で、x86_64
アーキテクチャで動作する iOS シミュレーター向けにアーカイブをビルドする方法について紹介します。結論としては、fastlane の xcodebuild コマンド を用いて、SDK のパラメータに Simulator用 SDK を指定してあげることで実現できます。
背景
今回、手元に iPhone の実機が手元にない開発メンバーでも動作確認できるように iOS Simulator で動くアーカイブ(.app
ファイル)が必要になった、という経緯がありました。
以前から、CI上で App Store へアップロードするための実機向けのアーカイブ(.ipa
ファイル)をビルドして Artifacts に置いて共有する運用としており、この .ipa
ファイルを QA メンバーが参照して、そのビルド時点での動作を検証するなどしていました。
ただ、ここで共有されるアーカイブは実機の iPhone で動作する arm64
アーキテクチャ向けにビルドされたものであり、x86_64
アーキテクチャで動いている iOS Simulator では動作しません。そんな訳で、今回新しく iOS Simulator でも動作するアーカイブを生成し、共有する必要が出てきました。
iOS のデバイスごとのアーキテクチャの違いについてはこちらの記事が詳しいですので、ご参照下さい。
Fastfile に iOS Simulator をアーカイブする lane を追加する
以下の lane を追加しました。中身はシンプルで、xcodebuild コマンド を呼び出しているだけです。配信する予定のないアーカイブのため、署名作業は省いています。
lane :archive_for_simulator do xcodebuild( scheme: "MyApp", workspace: "MyApp.xcworkspace", configuration: "Release", sdk: "iphonesimulator", derivedDataPath: "build" ) end
指定しているパラメータ
パラメータ名 | 説明 |
---|---|
configuration | 構築する際に使用する Build Configuration を指定する。今回は、リリース時と同様の動作を見たいので、"Release"を指定。 |
sdk | プロジェクトの構築する際に使用するSDKの名前またはパスを指定する。今回は、"iphonesimulator"を指定。 |
derivedDataPath | 成果物を配置するパスを指定する。今回は、"build"ディレクトリを指定。 |
使用できる SDK の一覧を取得する
sdk
パラメータで指定できるSDK は、xcodebuild -showsdks
で調べることができます。
iphonesimulator13.4
というようにバージョン名も併せて指定することもできますが、このバージョンは CircleCI で指定している macOS イメージに依存しているため、あえて指定しないことで、自動的に追従できるようにしておくと良いと思います。
$ xcodebuild -showsdks iOS SDKs: iOS 13.4 -sdk iphoneos13.4 iOS Simulator SDKs: Simulator - iOS 13.4 -sdk iphonesimulator13.4 macOS SDKs: DriverKit 19.0 -sdk driverkit.macosx19.0 macOS 10.15 -sdk macosx10.15 tvOS SDKs: tvOS 13.4 -sdk appletvos13.4 tvOS Simulator SDKs: Simulator - tvOS 13.4 -sdk appletvsimulator13.4 watchOS SDKs: watchOS 6.2 -sdk watchos6.2 watchOS Simulator SDKs: Simulator - watchOS 6.2 -sdk watchsimulator6.2
成果物が配置されるパス
上記の例だと、derivedDataPath
にはbuild
ディレクトリを指定していますが、この場合は build/Build/Products/Release-iphonesimulator/
配下に MyApp.app
が配置されるようになります。
gym
コマンドを使っていない理由
余談ですが、もともと xcodebuild
コマンドではなく、 gym コマンド を用いてビルドしようとしていましたが、パッケージングの途中に発生するエラーが回避できなかったため、代替手段として xcodebuild
コマンドを使用しています。
.circle/config.yml
に iOS Simulator 向けのアーカイブを生成するジョブを追加する
最終的には以下のような設定としました。
version: 2.1 executors: default: macos: xcode: "11.3.0" shell: /bin/bash --login -eo pipefail environment: FL_OUTPUT_DIR: /Users/distiller/project/output jobs: archive_for_simulator: executor: default steps: - run: mkdir $FL_OUTPUT_DIR - checkout - setup_ruby_gems - setup_cocoapods - run: name: Archive for simulator command: bundle exec fastlane archive_for_simulator - run: name: Compress the archive command: | cd build/Build/Products/Release-iphonesimulator/ zip -r MyApp.app.zip MyApp.app mv MyApp.app.zip $FL_OUTPUT_DIR/ - store_artifacts: path: /Users/distiller/project/output
ジョブの最後で、生成されたアーカイブを Artifacts にストアするようにしています。
このジョブをリリースする際の Workflow から呼び出してあげることで AppStore にアップロードされるアーカイブ相当、かつ、iOS Simulator で実行・確認できるアーカイブを生成・共有できます。
さいごに
昨今の情勢では、開発チーム全員が在宅勤務のため開発に使用できる実機デバイスが手元になく、QAメンバーが試験できないというケースも少なくないと思います。その際、暫定対策的に iOS Simulator を用いて試験を実施する場合は、この記事のような方法が応用できると思います。
参考
CircleCI - macOS コンテナ上で利用できる iOS Simulator を調べる
はじめに
CircleCI 上で UI Test を実行する際に任意の iOS Simulator を指定することがあると思います。指定するにあたって、macOS のコンテナ上で事前に用意されている iOS Simulator を把握する方法について紹介します。
結論としては、CircleCI のジョブに SSH 接続して、 xcrun simctl list devices
コマンドを実行することで利用できる Simulator の一覧を取得できます。
指定するXcode バージョンによって利用できる Simulator のラインナップが違う
ここから各macOSコンテナインストールされているソフトウェアを確認できます。
Runtimes と Devices を確認すると、利用できるOS バージョンとデバイスが分かります。ここで注意したいのは、デバイスによっては使えるOSが限定されているので(例えば、iPhone SE と iOS13.3 の組み合わせは存在しない)、それらの組み合わせを具体的に把握したい場合は、xcrun
コマンドで調べる必要があります。
xcrun simctl list devices
で、利用できる Simulator を一覧する
xcrun simctl
は、Xcode に含まれる標準CLIで、主に Simulator をコマンドラインで操作・管理するための機能を備えています。
xcrun simctl list devices
を実行してみます。
$ xcrun simctl list devices == Devices == -- iOS 12.2 -- iPhone 5s (1AF03B24-D667-4FA0-ABAD-6B5D255D7C42) (Shutdown) iPhone 6 Plus (31733952-2624-41B9-9CB9-BBF9A6A1BE8B) (Shutdown) iPhone 6 (75133BAE-AE13-405F-AB5A-F0BB0A3B1E98) (Shutdown) iPhone 6s (2692F9AD-1EBE-4FA0-B4DB-4A3F47ECA097) (Shutdown) iPhone 6s Plus (2EDEC588-1BC1-465C-BE77-1D58EE9ECE03) (Shutdown) iPhone SE (28CD4003-D4AC-4F97-BFDF-00AFDFE3F08E) (Shutdown) iPhone 7 (7339D766-17AB-4A8D-8214-E93657B0200B) (Shutdown) iPhone 7 Plus (671C4D3C-AB9E-481C-9D7D-3BCA62CAF5D9) (Shutdown) iPhone 8 (C43A0A30-A28D-4647-8B37-CD17B027E119) (Shutdown) iPhone 8 Plus (4CE0ADB9-F373-4723-9EAD-235BB7AA7690) (Shutdown) iPhone X (302D33E0-44B1-4105-8914-3C3BAF964AC3) (Shutdown) iPhone Xs (3200B852-8926-43DA-B4EC-D5A75F9E0FB6) (Shutdown) iPhone Xs Max (FCB63BD0-4FAC-4A0C-812E-80BD83CDD8C3) (Shutdown) iPhone Xʀ (D2B8D5FB-7100-47A1-BCF5-7017B372FF7A) (Shutdown) iPad Air (FFBA7D98-F642-4254-9372-423C3E48E43D) (Shutdown) iPad Air 2 (F5D49A9C-C2A9-4124-B988-5139A2401F58) (Shutdown) iPad Pro (9.7-inch) (50EABE28-EF6D-4AF3-B9A1-7B93F019112B) (Shutdown) iPad Pro (12.9-inch) (7CE07B4D-0A2F-44E2-A7BC-DC55EC35FB72) (Shutdown) iPad (5th generation) (48C502FB-7BC7-42C0-A07D-86D965A2914F) (Shutdown) iPad Pro (12.9-inch) (2nd generation) (CA495D19-CAEE-4885-9F81-99585E6926CA) (Shutdown) iPad Pro (10.5-inch) (86133C43-0127-444B-9EB7-2BD67A938306) (Shutdown) iPad (6th generation) (4125706C-FA8C-4720-B6E0-7283F29DE081) (Shutdown) iPad Pro (11-inch) (FAAA7E3B-DA38-4999-A89B-FC25D113F8B9) (Shutdown) iPad Pro (12.9-inch) (3rd generation) (CFDC9512-38A2-4CA3-AD7D-435456355D8C) (Shutdown) iPad Air (3rd generation) (38894DEB-5BCE-42EA-9AF9-D4548EDAC881) (Shutdown) -- iOS 12.4 -- iPhone 5s (046A9C21-AFD5-434D-9B61-EE18A73D674A) (Shutdown) iPhone 6 Plus (C9D65D15-39CD-45E3-9964-1C05F5A153AB) (Shutdown) iPhone 6 (B21C342B-B25E-4B3F-A836-6C9D514E0903) (Shutdown) iPhone 6s (FD0E061D-67E3-489B-9E36-4D41E2795EBD) (Shutdown) iPhone 6s Plus (601197A5-AF38-4937-B66C-E7AC63F7A982) (Shutdown) iPhone SE (825FCF5E-E65E-4D72-A549-D08BDE6560B6) (Shutdown) iPhone 7 (6C11E32B-1DB7-4BEA-BCF7-FD296E0298C9) (Shutdown) iPhone 7 Plus (6B704ABA-AF68-48CF-9359-509E1A3D29B3) (Shutdown) iPhone 8 (44955F91-CE61-44E8-AF4E-DBD00ECFDB61) (Shutdown) iPhone 8 Plus (7C58677A-B157-4D3A-A6A6-05D67C76B856) (Shutdown) iPhone X (014774AE-D600-451D-BF0B-300E6A2FF9E1) (Shutdown) iPhone Xs (76431C2E-B0E1-434F-AF14-9BA0F9F8EEBA) (Shutdown) iPhone Xs Max (A05F76CD-98E8-4847-91E6-4252A039B1B3) (Shutdown) iPhone Xʀ (A0C5B344-26B8-493F-BCD2-58DA441EC5A8) (Shutdown) iPad Air (8533D0C7-BF40-4F92-808F-8F06E1A698A3) (Shutdown) iPad Air 2 (07690BC7-DA6A-46A4-940E-EC057B83B29E) (Shutdown) iPad Pro (9.7-inch) (AE20F740-74C5-4DAC-8BAB-8C1332F1F55D) (Shutdown) iPad Pro (12.9-inch) (17CA1C31-5A2B-4E2F-B5BC-C9800A8F1678) (Shutdown) iPad (5th generation) (DA04D45E-D445-4998-B928-E3AE410AC062) (Shutdown) iPad Pro (12.9-inch) (2nd generation) (10C0C185-B40B-416C-A2D7-4A0B72AAB49F) (Shutdown) iPad Pro (10.5-inch) (4EB6A598-7D54-49D9-AAC1-B6A093C8A214) (Shutdown) iPad (6th generation) (8576A2FC-2A4D-4939-94A8-949E4970EB00) (Shutdown) iPad Pro (11-inch) (BCD341B2-F33D-4D98-940E-B423500FA95B) (Shutdown) iPad Pro (12.9-inch) (3rd generation) (B6B052AF-65C1-43AB-8D3A-D8EB7A9BFF06) (Shutdown) iPad Air (3rd generation) (C7A80B69-5CA9-4A08-A9A8-471DBCABE502) (Shutdown) -- iOS 13.3 -- iPhone 8 (24B098E9-40FA-4FAE-A84B-CAB08F19FEDE) (Shutdown) iPhone 8 Plus (06AD8C58-1FE4-4696-BA9A-B4E44D36FAC7) (Shutdown) iPhone 11 (B755A75C-FF5D-4392-A97D-7C8C6A272579) (Shutdown) iPhone 11 Pro (651EA440-C365-4043-BE5F-AAEA79A32469) (Shutdown) iPhone 11 Pro Max (43308B24-0726-46A7-B8F5-584682FBC83D) (Shutdown) iPad Pro (9.7-inch) (49AA7AB9-4BD2-441B-9BE9-9A1BF2002CA3) (Shutdown) iPad (7th generation) (97E6DA3C-984B-4720-8AAF-BE17A427085C) (Shutdown) iPad Pro (11-inch) (2AD0CF74-FD8D-4A23-BD3B-22A4C0FEAB44) (Shutdown) iPad Pro (12.9-inch) (3rd generation) (5CC57CFA-AE46-4FB7-B656-E7FD1E9994EC) (Shutdown) iPad Air (3rd generation) (9F710327-DAFB-4D6B-8197-445DCCAAFFB7) (Shutdown) -- tvOS 12.4 -- Apple TV (F0706864-3933-4D65-87F5-93E4EB602074) (Shutdown) Apple TV 4K (0403E229-3B97-4F69-96B1-5CD5077A4A50) (Shutdown) Apple TV 4K (at 1080p) (CFA38739-EB05-4972-8991-A1E42219704A) (Shutdown) -- tvOS 13.3 -- Apple TV (6A22CD8B-CD2F-48A8-974F-2ADB31C3F9D9) (Shutdown) Apple TV 4K (47432A98-CAA1-425B-A7FD-2D9D07E5C2A2) (Shutdown) Apple TV 4K (at 1080p) (EAF9A428-4A71-4062-8963-20E5465A540C) (Shutdown) -- watchOS 5.3 -- Apple Watch Series 2 - 38mm (665441D3-061D-4F3E-8D18-EC544060D43D) (Shutdown) Apple Watch Series 2 - 42mm (1D9027F4-73CA-4559-B247-19FC32BE3D5B) (Shutdown) Apple Watch Series 3 - 38mm (99C85FB8-5E99-4664-B1A6-D96BFD67888F) (Shutdown) Apple Watch Series 3 - 42mm (12A4DE0A-ABCF-40BF-B79E-9EBB33E06B21) (Shutdown) Apple Watch Series 4 - 40mm (63AB7682-E481-4A3C-84A9-3732EB4355B3) (Shutdown) Apple Watch Series 4 - 44mm (6AF06CD2-F876-484A-BCB6-92891A186AC0) (Shutdown) -- watchOS 6.1 -- Apple Watch Series 4 - 40mm (5DC1AF52-C03B-4D58-BA4E-21E3DB926BAF) (Shutdown) Apple Watch Series 4 - 44mm (7C1CB39D-A7D4-4A1A-BE58-A021A4DEFDB5) (Shutdown) Apple Watch Series 5 - 40mm (93698ADC-D708-4BDB-958B-2CD8FBFC14C2) (Shutdown) Apple Watch Series 5 - 44mm (6D4B4A49-2C32-4FB0-88E1-90DD5DF829F9) (Shutdown)
各 OS バージョンで利用可能なデバイスが一覧されました。端末名の後ろに続く文字列は、Simulator を一意に特定するための UDID と現在の状態です。
fastlane の scan 機能の run_tests
に端末を指定する devices パラメーターがありますが、ここでは iPhone 11 Pro Max (13.2)
のように #{device} (#{version})
という規則で端末を指定して、配列で渡してあげると良いと思います。
さいごに
当初、 UI Test を実行する際に「この端末のこのバージョンの組み合わせはあるだろ〜」と雰囲気で Simulator を指定して、'iPhone 11 Pro Max (12.4)', couldn’t find matching simulator
と怒られたという経緯があり、ちゃんと調べることに至りました。何事も、雰囲気でやってはいけないですね。。
参考
CircleCI - コミットメッセージに特定の文字列が含まれていれば、ある処理を行う
はじめに
この記事では、CircleCI で「コミットメッセージに特定の文字列が含まれていれば、ある処理を行いたい」場合の実現方法について紹介します。結論としては、circleci-agent step halt
コマンドを利用することで比較的シンプルに実現できます。
今回は、CircleCI 2.1 で動作確認を行っています。
実現したかったこと
これまで、iOS プロジェクトにおいて以下のルールでワークフローを定義していました。
- master 以外のブランチは、
Unit test
を実行する - master ブランチは、
Unit test
に加えて、UI test
とApp Store Connect へのデプロイ
を実行する
これに加えて、新たに以下のルールを追加しました。
- master 以外のブランチは、コミットメッセージに「kick-ui-test」という文字列が含まれていれば、
Unit test
のあとにUI test
を実行する
定義したワークフローの全体像
図: master ブランチ以外の Workflow |
図: master ブランチの Workflow |
yaml の全体像
※ 各ジョブ内で行っている処理(呼び出している commands)については省略して例示します。
version: 2.1 jobs: unit_test: <<: *defaults steps: - unit-test run_ui_test_as_needed: <<: *defaults steps: - checkout - run: name: Decide whether to run ui-test, depending on the content of the commit message. command: | COMMIT_MESSAGE=$(git log -1 HEAD --pretty=format:%s) TRIGGER_MATCH_PATTERN="^.*kick-ui-test.*$" if [[ ${COMMIT_MESSAGE} =~ ${TRIGGER_MATCH_PATTERN} ]]; then echo "Continue to run ui_test, as the commit message contains kick-ui-test." else echo "Since the commit message does not include kick-ui-test, this job will be successfully terminated." circleci-agent step halt fi - ui_test ui_test: <<: *defaults steps: - ui_test deploy_appstore: <<: *defaults steps: - deploy workflows: build-workflow: jobs: - unit_test - run_ui_test_as_needed: requires: - unit_test filters: branches: ignore: - master - ui_test: filters: branches: only: - master - deploy_appstore: requires: - unit_test - ui_test filters: branches: only: - master
run_ui_test_as_needed
ジョブについて
このジョブで 「コミットメッセージに「kick-ui-test」という文字列が含まれていれば、 Unit test
のあとに UI test
を実行する」という制御を実現しています。
run_ui_test_as_needed: <<: *defaults steps: - checkout - run: name: Decide whether to run ui-test, depending on the content of the commit message. command: | COMMIT_MESSAGE=$(git log -1 HEAD --pretty=format:%s) TRIGGER_MATCH_PATTERN="^.*kick-ui-test.*$" if [[ ${COMMIT_MESSAGE} =~ ${TRIGGER_MATCH_PATTERN} ]]; then echo "Continue to run ui_test, as the commit message contains kick-ui-test." else echo "Since the commit message does not include kick-ui, this job will be successfully terminated." circleci-agent step halt fi - ui_test
主に shell script で以下の処理を行っています。
- 直近のコミットメッセージを取得
- 正規表現で、特定したい文字列(今回であれば
kick-ui-test
)を定義 1.
で取得したコミットメッセージに特定文字列が含まれているかを判定- もし、含まれていれば何もしないことを
echo
するだけで、次のステップへ進む - もし、含まれていなければその時点でジョブを正常終了する
- もし、含まれていれば何もしないことを
ui_test
を実行する
3.2 で、ジョブを正常終了させる際には、circleci-agent step halt
コマンドを利用しています。このコマンドは、ジョブを失敗させずに終了させることができるもので、ジョブを条件付きで実行する必要がある今回のようなケースに便利です。
Ending a Job from within a step | CircleCI
さいごに
今回は、master ブランチ以外(機能の実装途中など)でも手軽に ui_test
を実行したいケースがあり、run_ui_test_as_needed
job を追加しました。
CircleCI 2.0 から次のジョブを続行する前に手動による承認操作を待つ制御が可能になりましたが、仕組み上、手動で承認する手間が発生するため、今回のようにコミットする時点で特定ジョブの実行を制御できると便利だと感じました。
参考
fish - chruby で最新の Ruby をインストールする
fish 環境に chruby をインストールする方法をまとめます。
実行環境
> sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G1012 > brew -v Homebrew 2.1.16 Homebrew/homebrew-core (git revision 92599; last commit 2019-11-26) > fish -v fish, version 3.0.2
homebrew で chruby を導入
> brew install chruby
chruby-fish を導入
fish で chruby を利用するためのプラグインをインストールする。
> brew install chruby-fish
設定ファイルを読み込むために ~/.config/fish/config.fish
に以下を追記。
source /usr/local/share/chruby/chruby.fish source /usr/local/share/chruby/auto.fish
設定を反映させる。
> source ~/.config/fish/config.fish
ruby-install で最新の Ruby をインストールする
記事執筆時点(2019年11月26日)での最新の Ruby を導入します。
> ruby-install ruby Successfully installed ruby 2.6.5 into /Users/username/.rubies/ruby-2.6.5 > chruby ruby-2.4.9 ruby-2.6.5
プロジェクトで利用する Ruby バージョンを指定する
プロジェクトのルートディレクトリに .ruby-version
ファイルを作成し、以下を追記。
2.6.5
バージョンが自動で切り替わるかを確認する
> ruby --version ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18] > cd example_ruby_project/ > cat .ruby-version 2.6.5 > ruby --version ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin18]
ディレクトリに配置された .ruby-version
を見て、Ruby のバージョンが切り替わっているのが分かります。
以上です。
参考
Android - FileProvider で外部アプリとファイルを共有する
FileProvider とは
Android7.0(API24, Android N)から、ファイルシステムに大きな変更が加わり、権限の仕様がより厳しいものへと変更されました。
API24 以降向けのアプリにおいて、プライベートディレクトリにアクセス制限が加わり、外部から存在、サイズ、メタデータなどの漏洩を防ぐことができます。
この権限の変更により以下のような副作用があります。
- プライベートファイルの所有者は
MODE_WORLD_READABLE
およびMODE_WORLD_WRITABLE
を使用したパーミッションの緩和ができず、実行しようとするとSecurityException
が発生する - 開発しているアプリのパッケージドメイン以外の
file://
URIを渡すと、受け取り手がアクセスできないパスとなるため、 外部のアプリとのプライベートなファイルの共有には FileProvider の使用が推奨される
このように Android7.0 以降向けのアプリでは、Androidフレームワークによって自身のアプリ以外への file://
URIの公開ができず、 content://
URIへ変換し一時的なパーミッションを付与した上で URI をやりとりする必要があります。
ファイルに対してパーミッションを付与したり、 file://
から content://
へURIへ変換する最も簡単な方法は FileProvider クラスを使用することです。
FileProvider を用いてアプリ間のファイル共有を実現する
繰り返しとなりますが、自身のアプリから別のアプリにファイルを安全に共有するには、 content://
URIの形式でファイルをハンドルできるようにアプリを構成する必要があります。
Android フレームワークの FileProvider コンポーネントは、XML で指定した仕様に基づいてファイルのコンテンツ URI を生成します。具体的な手順を以下で説明していきます。
FileProvider の使用を AndroidManifest で宣言する
まず、 AndroidManifest.xml
にエントリーを追加します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.mobileapp"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider" /> </provider> ... </application> </manifest>
指定している各属性について、説明します。
android:authorities 属性
アプリのパッケージドメインをプレフィックスとした命名でURIの権限を設定します。ユニークである必要があるため、 ${applicationId}.provider
などの名前が良いと思います。
android:exported 属性
FileProvider は公開する必要がないため false
とします。
android:grantUriPermissions 属性
外部からのファイルへのアクセスを一時的に許可できるようにします。今回は、外のアプリとプライベートファイルを共有したいため、 true
とします。
共有するディレクトリを宣言する
共有するファイルを配置するディレクトリを指定します。 res/xml
以下に file_provider.xml
ファイルを作成し、以下の内容で記述します。
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="photos" path="photos/"/> </paths>
この例では、 <files-path>
タグでアプリの内部ストレージである files/
ディレクトリ以下のサブディレクトリを共有するようにしてます。このサブディレクトリのパスは、 Context.filesDir
で取得できます。この他にも、以下のようなタグが指定可能です。
<cache-path>
- 内部ストレージのキャッシュを共有でき、パスは
Context.cacheDir
で取得できる
- 内部ストレージのキャッシュを共有でき、パスは
<external-path>
- 外部ストレージのルートにあるファイルを共有でき、ルートパスは
Environment.getExternalStorageDirectory()
で取得できる
- 外部ストレージのルートにあるファイルを共有でき、ルートパスは
<external-files-path>
- 外部ストレージのルートにあるディレクトリを共有でき、パスは
Context.getExternalFilesDir()
で取得できる
- 外部ストレージのルートにあるディレクトリを共有でき、パスは
<external-cache-path>
- 外部ストレージにあるキャッシュを共有でき、パスは
Context.externalCacheDir
で取得できる
- 外部ストレージにあるキャッシュを共有でき、パスは
<external-media-path>
- 外部メディアにあるディレクトリを共有でき、パスは
Context.externalMediaDirs
で取得できる
- 外部メディアにあるディレクトリを共有でき、パスは
また、タグに含まれる属性については説明します。
name="name"
URIパスのセグメント。この値は、生成されるURIのパスに含まれるものです。
path="path"
共有するサブディレクトリ。値はサブディレクトリ名であり、個々のファイル名ではないことに注意します。ファイル名で単一のファイルを共有したり、ワイルドカードを使用して指定することもできません。
生成されるURIを見てみる
以下のように、複数のパスを指定することもできます。下記の定義の場合、生成されるURIと実態のあるコンテンツのパスを見てみます。(Providerの指定は冒頭でAndroidManifestファイルで定義したものを想定)
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="my_images" path="images/"/> <files-path name="my_docs" path="docs/"/> </paths>
共有のコンテンツのURIは、
content://com.example.mobileapp.provider/my_images/example.jpg
content://com.example.mobileapp.provider/my_docs/example.pdf
コンテンツの実態のパスは、
com.example.mobileapp/files/images/example.jpg
com.example.mobileapp/files/docs/example.pdf
となります。
ファイルからコンテンツURIを生成する
コンテンツURIを使用してファイルを他のアプリと共有する場合は、FileProvider を使用して URI を生成する必要があります。具体的には以下のステップを踏みます。
具体的なコードは以下のようになります。
val captureFile = this.createOutputFile() val contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", captureFile)
getUriForFile(Context context, String authority, File file)
によって、共有コンテンツURIを生成してくれます。
例えば、画像ファイルを共有したい場合は createOutPutFile()
の中身は以下のようになります。
fun createOutputFile(): File { val timeStamp = DateFormat.format("yyyyMMdd_HHmmss", Date()).toString() val tempFile = File( this.activity.filesDir, "/my_images/$timeStamp.jpg") if (!tempFile.exists()) { try { tempFile.parentFile.mkdirs() tempFile.createNewFile() } catch (e: IOException) { e.printStackTrace() } } return tempFile }
ここで注意したいのは、File はインスタンス化した後、実態としてストレージに空の状態で一時保存することです。
そのためにこの関数内では /my_images
ディレクトリの存在を確認し、なければ親ディレクトリとして生成し、その配下にタイムスタンプをファイル名として tempFile.createNewFile()
しています。
外部アプリへコンテンツURIを渡す
今回は、前項で生成した画像形式の一時ファイルをカメラアプリへ共有してみます。具体的なコードは以下です。
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) this.cameraContentUri = createOutputUri() cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, this.cameraContentUri) cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) this.activity.startActivityForResult(cameraIntent, REQUEST_CODE)
重要なのは、外部アプリに対して共有コンテンツへの一時的なアクセス許可を与えてあげることです。今回は書き込み権限を与えるために Intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
を指定しています。
以上が、FileProvider を用いたアプリ間のファイル共有の手順です。
参考文献
fish - rbenv で最新の Ruby をインストールする
fish 環境に ruby の開発環境を構築する方法をまとめます。
実行環境
> sw_vers ProductName: Mac OS X ProductVersion: 10.14.1 BuildVersion: 18B75 > brew -v Homebrew 1.8.2
homebrew で rbenv を導入
> brew install rbenv
fish で rbenv を使う設定
vi ~/.config/fish/config.fish
に以下を追記
## rbenv init setting status --is-interactive; and source (rbenv init -|psub)
設定を反映させる
> . ~/.config/fish/config.fish
rbenv で最新の ruby を導入する
今回は 2.4.5
(2018年11月16日現在の最新)をインストールします。
> rbenv install --list Available versions: 1.8.5-p52 1.8.5-p113 1.8.5-p114 1.8.5-p115 1.8.5-p231 ... > rbenv install 2.4.5 ruby-build: use openssl from homebrew Downloading ruby-2.4.5.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.5.tar.bz2 Installing ruby-2.4.5... Installed ruby-2.4.5 to /Users/hoge/.rbenv/versions/2.4.5
使用したい version を設定する
> rbenv versions * system (set by /Users/hoge/.rbenv/version) 2.4.5 > rbenv global 2.4.5 > rbenv versions system * 2.4.5 (set by /Users/hoge/.rbenv/version) > ruby -v ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-darwin18]
以上です。