애플 플랫폼 앱의 경우, 테스트플라이트를 통해서 테스트 빌드를 배포하는 경우가 많다. 그런데 이게 IPA 파일을 업로드하고 앱스토어커넥트 내부에서 뭔가 조치를 취한 후에 사용이 가능해지기 때문에 업로드 이후에도 몇십분 정도 기다려야 앱을 설치할 수 있게 된다. 몇십분이라고 해봐야 조금만 기다리면 되는 일이긴 하나, 팀의 내부 사정에 따라 업로드한 앱을 바로 설치해서 테스트해보고 싶은 경우가 많기 때문에 테스트플라이트 대신 다른 방법을 쓰는 경우도 꽤 된다. 예를 들면 Firebase App Distribution이나 App Center Distribute를 사용하는 것이다(And so on…).
이 방법들은 자기들 서버에 IPA 파일을 올려두고 테스터의 기기에서 해당 파일을 설치하는 방법을 사용하기 때문에 테스트플라이트 배포와 달리 업로드 직후 바로 설치가 가능한데, 문제는 IPA 파일을 생성할 때 사용한 프로필에 등록된 기기에서만 설치할 수 있다는 것이다. 이 경우 코드 변화 없이 새 테스터만 추가해야할 경우 다시 IPA 파일을 생성하기가 매우 번거롭다는 문제가 있다. 물론 변경된 프로필을 받아와 IPA 파일을 다시 서명하면 되지만 처음에는 이 방법을 몰라서 시간을 많이 날려먹었더랬다. 여튼 기본적으로는 아래와 같은 방법으로 서명을 다시 할 수 있다.
- ipa 파일 unzip하기(잘 모르겠으면 걍 finder에서 확장자 바꾸고 압축해제하면 됨)
- 압축해제된 경로로 들어가 해당 앱의 코드 시그니쳐를 삭제한다 (
Payload/{YourAppName}.app/_CodeSignature/
) - 재서명할 프로비져닝 프로필을 앱 경로(
Payload/{YourAppName}.app/
)에embedded.mobileprovision
이름으로 붙여넣기 - codesign 유틸리티로 서명하기
- 앱을 다시 압축하기
설명이 다소 불친절하다고 생각할 수 있는데, 다 이유가 있다. 그냥 fastlane의 resign 명령어를 쓰면 되기 때문이다. 위에 설명해둔 것은 resign 명령을 했을떄 뭔 일이 일어나는지 대강이라도 알면 좋을것 같아서… 여튼 fastlane의 경우 다음과 같이 할 수 있다.
# Fastfile 내부
lane :some_resign_action do
resign(
ipa: "재서명할 ipa 파일 경로",
signing_identity: "앱 서명 identity",
provisioning_profile: {
"app.bundle.id" => "재서명할 프로필 경로"
}
)
end
참고로 앱 서명 identity과 정확히 뭔지 잘 모르겠으면 해당 프로젝트를 xcode에서 열어서 Signing & Capability 탭으로 이동해 Signing Certificate가 뭔지 확인하면 된다. 아니면 아래 커맨드라인 명령을 통해 현재 기기에 존재하는 서명을 찾을 수 있다.
$ security find-identity -v -p codesigning
근데 또 이제 매번 최신 프로필을 받아와서 경로를 입력하고 어쩌고 저쩌고 하기가 귀찮을 수 있다. 그런 경우에는 match 설정을 미리 해두면 좀 더 간편하게 처리할 수 있다. match 명령어를 실행할 때 아래와 같이 사전에 설정한 경로에 프로필을 저장하도록 하는 것이다.
# Fastfile 내부
lane :some_match_action do
match(
readonly: true,
type: "development" # 이 외에도 adhoc, release 등...
app_identifier: "app.bundle.id",
output_path: "다운받을 경로"
)
end
이런식이면 지정된 경로에 프로비져닝 프로필이 다운로드된다. 그럼 이 명령어와 저 위의 resign 명령어를 조합해 항상 새롭게 갱신된 프로필로 재서명할 수 있다.
# Fastfile 내부
lane: :resign do |options|
ipa_path = options.fetch(:ipa_path, "") # ipa_path라는 아규먼트를 정의해서 재서명할 ipa 파일의 경로를 알려준다.
if ipa_path.empty?
UI.user_error!("ipa_path 값을 입력해주세요")
end
# 사용될 변수값을 정의한다. 꼭 여기서 정의할 필요는 없고 그냥 이런 식의 변수를 쓸 수 있다는 것을 보여주기 위함. 만약 앱의 scheme을 여럿 쓰고 있다면 ipa_path와 마찬가지로 options에서 scheme을 받아서 이 부분에서 switch문을 통해 각기 다른 값을 넣어주는것을 추천한다.
type = "development"
bundle_id = "app.bundle.id"
profile_path = "프로필이 저장되어있는 경로"
profile_filename = "프로필 파일명"
signing_identity = "서명 ID"
match(
readonly: true,
type: type,
app_identifier: bundle_id,
output_path: profile_path
)
resign(
ipa: ipa_path,
signing_identity: signing_identity,
provisioning_profile: {
bundle_id => "#{profile_path}/#{profile_filename}"
}
)
end
이와 같은 방식으로 명령을 지정하면 새 테스터가 추가될 때 마다 이전 빌드의 IPA 파일을 가져와서 match를 통해 저장되어있는 가장 최신의 프로필을 받아오고, 이 프로필을 정해진 ipa 파일에 재서명하면 된다. 아마 Firebase 나 AppCenter에서 최신 빌드 파일을 받는 방법을 제공할 것 같긴 한데 그거는 또 플랫폼별로 방법이 다를거라서 각 플랫폼의 문서를 참고하는 게 좋겠다.