酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

macOS Sonoma (14.0)で OS X El Capitan の USBインストーラーを作成する

MacBook Air (11-inch, Mid 2011)でリカバリができなくなってしまった。

内部ストレージとインターネットの両方で再インストールが失敗。MacBook Pro (2021)を使用してMac OS X El CapitanのUSBインストーラーを作成することにした。

本当はMBAの初期OSである Lion をインストールしたかったが、後述の理由により諦めてEl Capitan にした。今後も、AppleシリコンのMacBook で、IntelプロセッサのMacBook向けのUSBインストーラーを作成することはないとは思うが、同じ境遇の方のために備忘録としてメモを残す。

あらすじ:MacBook Air のリカバリができない!

MacBook Airを初期化しようとディスクユーティリティでストレージの削除後、Mac OS Xの再インストール時にエラーが発生した。通常であれば「Command+R」でリカバリーモードに入れるはずだが、地球儀に警告マーク「2100F」エラーが出てしまい先に進めない。

「Option+Command+R」や「Command+R」でそれぞれインターネット経由と内部ストレージ経由の復旧が可能なだが、なぜかどちらのキーバインドでもインターネット経由でのリカバリとなってしまう謎の現象が発生した。また、「2100F」エラーが発生しない場合でも「必要な追加コンポーネントをダウンロードできません」とのエラーメッセージが表示されてしまい、Mac OS X の再インストールに失敗してしまう。

インターネット経由でのリカバリは諦めて、USBインストーラーを作成することにした。

環境

USBインストーラーを作成する環境と初期化したい環境は以下の通り。

  • USBインストーラーを作成するMacBook Pro
    • Apple シリコン
    • MacBook Pro 2021
    • macOS Sonoma (14.0)
  • 初期化したい MacBook Air
    • Intel プロセッサ
    • MacBook Air (11-inch, Mid 2011)

OS X El Capitan USB インストーラーを作成する

Mist」を使用してOS X El Capitanのアプリケーションデータを生成する。Mistを使ったUSBインストーラーの作成方法は過去に紹介したことがある。

このように「Mist」はUSBインストーラーも作成できるツールだが、ちょっと古めの Mac OS X のインストーラーは作成できないという制約もある。

Mistでは「Install OS X El Capitan 10.11.6_15G31.app」の作成のみをおこない、USBインストーラーの作成には createinstallmedia コマンドを使うことにした。createinstallmedia コマンドはインストーラーに含まれるユーティリティで、USBインストーラーを作成してくれる。

createinstallmedia コマンドに対応しているのは、OS X El Capitan 以降なので Lion は諦めて、El Capitan のインストーラーを作成することにした。

デスクトップにインストーラーを配置した状態で、sudo INSTALLER.app/Contents/Resources/createinstallmedia --volume /USB_DEVICE_PATH --applicationpath INSTALLER.app を実行する。

El Capitanの場合は以下の通りであった。

sudo /Users/ch3cooh/Desktop/Install\ OS\ X\ El\ Capitan\ 10.11.6_15G31.app/Contents/Resources/createinstallmedia --volume /Volumes/OS\ X --applicationpath /Users/ch3cooh/Desktop/Install\ OS\ X\ El\ Capitan\ 10.11.6_15G31.app

以上で MBA に無事 OS X El Capitan をインストールすることができた。

Xcode 15.0 アップデート後、R.swift の ColorResource や ImageResource の extension でエラーが発生する

さくさんは Assets.xcassets や Colors.xcassets にアクセスするために R.swift を愛用している。Xcode 15.0 アップデート後、R.swift の ColorResource や ImageResource の extension でエラーが発生するようになった。

R.swift の ColorResource や ImageResource の extension でエラーが発生する

エラーメッセージには 'name' is inaccessible due to 'fileprivate' protection level と書かれている。

エラーが発生している extension は以下の通りである。

extension ColorResource {
    var color: Color {
        Color(name)
    }
}

該当のIssueによると、Xcode 15の生成するColorResourceがコンフリクトを引き起こしエラーが発生してしまう。したがって、以下の手順でRswiftResources.ColorResourceの拡張を明示的に指定することで、エラーが解消される。

extension RswiftResources.ColorResource {
    var color: Color {
        Color(name)
    }
}

関連記事

Xcode 15.0 アップデート後、CocoaPods経由でインストールした Firebase Apple SDK でコンパイルエラーが発生する

Xcode 15.0へのアップデート後、CocoaPods経由でインストールしたFirebase Apple SDKでコンパイルエラーが発生する。

解決方法

CocoaPods 1.13.0 のリリースされると、この問題は解消される見込みである。ただし v1.13.0がリリースされるまでの間は、ワークアラウンドを利用しなければいけない。Podfileを開き、以下のワークアラウンドのコードを追加する。

post_install do |installer|
  # CocoaPods v1.13.0 がリリースされるまでのワークアラウンド
  installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
      xcconfig_path = config.base_configuration_reference.real_path
      xcconfig = File.read(xcconfig_path)
      xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
      File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
      end
  end
  
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
    end
  end
end

関連記事

macOSで librsvg を使って SVG から PNG へ変換する

大量にある SVGファイルを PNGファイルへ変換したい。

大量にある SVGファイルの例(例:国旗アイコン)

本記事では、rsvg-convert コマンドを使って、指定したディレクトリ内のSVGファイルをPNGファイルに変換する方法を以下に説明する。

rsvg-convert のインストール

まずは、rsvg-convertをインストールする。

macOSの場合は、Homebrewを使ってインストールするのが一番簡単だ。

brew install librsvg

SVGファイルをPNGに変換する

SVGファイルをPNGファイルへ変換するには以下のコマンドを利用する。-hと-wオプションを使用して、それぞれの出力画像の高さと幅を指定できる。

rsvg-convert -h 120 -w 120 svgfile.svg -o pngfile.png 

指定したディレクトリ内のSVGファイルをPNGファイルへ変換する

SVGファイルが少ない場合は、前述のコマンドをひとつひとつ実行していけばよいが、国アイコンのように 200を超えるファイルの変換を手作業でするのは無理がある。指定したディレクトリ内のSVGファイルをPNGファイルに一気に変換するシェルスクリプトを作成する。

以下のスクリプトは、指定されたディレクトリからSVGファイルを検索して、それぞれをPNGに変換していく。画像のサイズは -w-h オプションを使用して任意の値を設定している。画像サイズを変更する必要がなければ、オプションの指定は不要である。

このスクリプトを convert_svg_to_png.sh という名前で保存する。

#!/bin/bash

# 入力ディレクトリと出力ディレクトリを引数として受け取る
INPUT_DIR=$1
OUTPUT_DIR=$2

# 出力ディレクトリが存在しない場合、作成
if [ ! -d "$OUTPUT_DIR" ]; then
  mkdir -p "$OUTPUT_DIR"
fi

# 入力ディレクトリ内のSVGファイルを検索し、それぞれをPNGに変換
for svgfile in $INPUT_DIR/*.svg; do
  # ファイル名を取得(拡張子なし)
  filename=$(basename "$svgfile" .svg)
  
  # PNGファイル名を生成
  pngfile="$OUTPUT_DIR/$filename.png"
  
  # SVGをPNGに変換
  rsvg-convert -w 20 -h 20 "$svgfile" -o "$pngfile"
done

スクリプトを保存したあと、以下のコマンドを使用してスクリプトを実行できる。INPUT_DIRはSVGファイルが格納されているディレクトリであり、OUTPUT_DIRは変換されたPNGファイルを保存するディレクトリである。

convert_svg_to_png.sh INPUT_DIR OUTPUT_DIR

おまけ:SVG から PDF へ変換する

rsvg-convert を使って SVG から PDF へ変換することも可能だ。

rsvg-convert -f pdf svgfile.svg -o pngfile.png 

トラブルシューティング

スクリプトの実行時に permission denied が発生した場合は以下のように対応する。

chmod +x convert_svg_to_png.sh

Intel Macを初期化するために Mist を使用してUSBインストーラーを作成した

家庭内サーバーマシンをリプレイスした。いままで使っていた Mac mini (Late 2014) が不要になったため、Mist を使って USBインストーラーを作成して初期化した。

あらすじ

今年の3月に「ThinkCentre M715q Tiny」を譲ってもらった*1。しかし、デスクトップマシンは Raspberry Piと比較してセットアップが難しくて(面倒くさくて)、玄関に長い間放置していた。

重い腰を上げたのは7月末で、ようやくM715q TinyにJenkinsをインストールした。Intel Mac miniで実行していたSwift製のバッチをpythonに移植もした。Jenkins 経由でfrozenpandaman/s3sを実行できるようになったのは嬉しい。途中、WindowsストアからインストールしたpythonがJenkinsの実行ユーザーからアクセスできない問題に直面したが、pythonインストーラーを使うことで解決し、無事家庭内サーバーのリプレイスができた。

家庭内サーバーといってもテキスト処理くらいしかしていないが、前に使用していた8GBのメモリしか搭載していないIntel Mac miniではJenkinsが時々フリーズしていたのに対し、メモリを増設したM715q Tinyは24GBなので、フリーズすることもなく問題なく稼働している。

Mist を使って USBインストーラーを作成する

M1以降のAppleシリコンチップを搭載したMacでは、App Store からmacOS 12以下のOSインストーラーをダウンロードしてきて、USBインストーラーを作成する方法が使えないようだ。昔のmacOSをIntel Mac miniにインストールしたい場合は、Mistを利用してUSBインストーラーを作成するのがよさそうだ。

github.com

Mistを使用すれば、GUI上で好きなバージョンのmacOSを選び、インストーラーを簡単に作成することができる。このツールを使って、Appleシリコン搭載のMacでIntel Mac向けのUSBインストーラーを作成できるのはとても便利だ。

20分ほど席を離れているうちにUSBインストーラーができた。

Intel Mac mini の再インストール

その他のMacコンピュータの場合: Optionキーを押したまま電源ボタンを押してMacを起動します。

Macの起動ディスクを変更する - Apple サポート (日本)

今回Windowsキーボードを使っていたので、Altキーを押しっぱなしにして Mac mini の電源をつけることでブート選択画面を表示することができた。

ストレージにランダムデータを書き込んで読み出せないようにする

もしストレージとして HDD を使っているのであれば、ランダムデータを書き込んでデータを復元できないようにする。ただ、最近の Mac Book 及び Mac mini で内蔵HDDを搭載しているケースはないかと思うので気にしなくても良いかもしれない。

いつ頃からかディスクユーティリティから「完全消去」オプションがなくなった。ターミナルで以下のコマンドを実行すればランダムデータの書き込みをしてくれる。

diskutil list
diskutil randomdisk 1 /dev/disk0

macOSのインストール

ディスクユーティリティを使用してHDDにパーティションを作成した後、macOS Big Surのインストールを開始する。インストールには約1時間掛かるが、基本的には操作の必要がないため、他の作業をしながら待つことができる。

Cloud Firestore の Collection references は偶数個のセグメントだと例外が発生する

長いあいだ、Swift でのデータ操作が煩雑な Cloud Firestore を使う気になれなかったのだが、いつのまにか Swift Concurrency に対応していた。async / await が使えると話が変わってくる。格段に使いやすくなっていたので、お試しで簡単なアプリを作ってみることにした。

しかし、Firestore へデータを書き込もうとしたところ例外が発生してしまった。

Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Invalid collection reference. Collection references must have an odd number of segments, but version/1/z5NXjW6CTFdHjlNjsrWQ4XBxcvg2/items has 4'

「無効なコレクション参照です。コレクション参照には奇数のセグメントが必要です。」と言われても、どういうことかわからない…… なぜ偶数なら大丈夫なのか?

まとめ

先に答えを書いておく。

  • Firestore のパス設計は collection/document/collection/document としなければいけない
  • よってコレクションのリファレンスは、奇数のセグメントでなければいけない
    • 例:/version/1/users など
  • よってドキュメントのリファレンスは、偶数のセグメントでなければいけない
    • 例:/version/1 など
  • それ以外の場合、例外 FIRInvalidArgumentException が発生する

経緯

Firestore にデータを保存するため、以下のようなコードを書いた。

let documentRef = firestore.collection("version/1/\(userId)/items").document(item.id)
try await documentRef.setData(
    [
        "title": item.title,
        "create_at": item.createdAt,
        "update_at": item.updatedAt,
    ]
)

このエラーに関する情報は日本語では「【Swift】FireStoreの機能を使用すると、"Invalid document reference.."のエラー」でしか取り上げられていなかったが、回答としては db.collection("User").document(userDataClass.userID).getDocument()db.collection("User").getDocument()に変更したら動くようになったという曖昧な理由であった。

理屈がわからなかったため調べたところ、Firestoreのドキュメントのパス設計は collection/document/collection/document という交互のパターンにしなければいけない。よって、ドキュメントのリファレンスは必ず偶数のセグメント、コレクションのリファレンスは必ず奇数のセグメントを持つことになるため、

  • version/1/\(userId)/items/\(itemId) は、 collection/d/d/collection/d になっていしまうため例外が発生する
  • version/1/users/\(userId)/items/\(itemId) は、 collection/d/collection/d/collection/d なので問題なく書き込みが成功する

まとめに書いたように Firestore にはパス設計が存在するため注意すること。

iOSユニットテストでテスト専用リソースの組み込みと利用方法

たとえば画像識別アプリを開発中には、テスト画像を Bundle からロードして、画像を識別してその結果を評価するユニットテストを作成する。または、HTMLをスクレイピングするアプリを開発中には、ダミーのHTMLを Bundle からロードして、スクレイピング処理が正しく動いているか評価するユニットテストを作成する。

しかし、これらのテスト用のリソースをアプリの本番バイナリには含めたくない。そこで、テスト時だけテスト用のリソースをBundleに組み込むというニーズが生じる。

この記事では、テスト時にのみ利用可能なリソースをどのようにして追加して利用するのかを紹介する。ここでの login.html は、テストバイナリに組み込まれた ダミーの HTML ファイルである。

テスト時のみ利用可能なようにリソースを追加する

テストターゲット XXXXTests フォルダの配下に login.html を追加する。

login.html の Target Membership は、ユニットテストで利用する XXXXTests にのみチェックをつける。

コードの例

テストバンドルの識別子を用いて Bundle オブジェクトを取得し、login.html ファイルにアクセスする。テストバイナリの Bundle IDがjp.ch3cooh.AppTestslogin.html を読み取りたい場合、以下のようなコードとなる。

let bundle = Bundle(identifier: "jp.ch3cooh.AppTests")
let url = bundle?.url(forResource: "login", withExtension: "html")
return try! Data(contentsOf: url!)

まとめ

この方法を使用することで、テスト時のみ特定のリソースにアクセスすることが可能になる。テストの際のモックデータや、テスト画像の読み込みに利用することができる。