Issue Description
Whenever I run upload_to_testflight
with fastlane
in a capacitor-powered app environment, I get the errors mentioned in the title of this issue and listed below:
----------------------------------
--- Step: upload_to_testflight ---
----------------------------------
Creating authorization token for App Store Connect API
Ready to upload new build to TestFlight (App: xxxxxx)...
Going to upload updated app to App Store Connect
This might take a few minutes. Please don't interrupt the script.
Transporter transfer failed.
ERROR ITMS-90713: Missing Info.plist value. A value for the Info.plist key 'CFBundleIconName' is missing in the bundle 'xxxx'. Apps built with iOS 11 or later SDK must supply app icons in an asset catalog and must also provide a value for this Info.plist key. For more information see http://help.apple.com/xcode/mac/current/#/dev10510b1f7.
ERROR ITMS-90022: Missing required icon file. The bundle does not contain an app icon for iPhone / iPod Touch of exactly '120x120' pixels, in .png format for iOS versions >= 10.0. To support older versions of iOS, the icon may be required in the bundle outside of an asset catalog. Make sure the Info.plist file includes appropriate entries referencing the file. See https://developer.apple.com/documentation/bundleresources/information_property_list/user_interface
ERROR ITMS-90023: Missing required icon file. The bundle does not contain an app icon for iPad of exactly '167x167' pixels, in .png format for iOS versions supporting iPad Pro. To support older operating systems, the icon may be required in the bundle outside of an asset catalog. Make sure the Info.plist file includes appropriate entries referencing the file. See https://developer.apple.com/documentation/bundleresources/information_property_list/user_interface
ERROR ITMS-90023: Missing required icon file. The bundle does not contain an app icon for iPad of exactly '152x152' pixels, in .png format for iOS versions >= 10.0. To support older operating systems, the icon may be required in the bundle outside of an asset catalog. Make sure the Info.plist file includes appropriate entries referencing the file. See https://developer.apple.com/documentation/bundleresources/information_property_list/user_interface
ERROR ITMS-90704: Missing App Icon. An app icon measuring 1024 by 1024 pixels in PNG format must be included in the Asset Catalog of apps built for iOS, iPadOS, or watchOS. Without this icon, apps cannot be submitted for review. For details, see https://developer.apple.com/ios/human-interface-guidelines/icons-and-images/app-icon/.
Tried and Failed attempts
- Info.plist Is Missing in Xcode 13 — Here’s How To Get It Back | by Andrew Zheng | Better Programming
- often cited Stackoverflow thread (old thread...), but without luck as well...
- It seems to be the number one copy & paste solution others share: https://discuss.bitrise.io/t/asset-validation-failed/20700, https://community.monogame.net/t/app-store-publishing-error/16624/2 and even here https://github.com/ionic-team/capacitor-assets/issues/231#issuecomment-956515912
- ios - In Xcode, my info.plist file doesn't see even though the icons are in the assest folder. What is the solution? - Stack Overflow (2021)
- use
XSAppIconAssets
https://community.monogame.net/t/app-store-publishing-error/16624/6 -> did not help!
Xcode files
I double-checked the icons, and to me, they seem to be correct:
The files are also visible in XCode without any warning sign in the asset catalog section:
Target Membership
is also set correctly to App
.
The settings visible below seem to be valid as well:
Contents.json
{
"images" : [
{
"size" : "20x20",
"idiom": "iphone",
"filename" : "[email protected]",
"scale": "2x"
},
{
"size" : "20x20",
"idiom": "iphone",
"filename" : "[email protected]",
"scale": "3x"
},
{
"size" : "20x20",
"idiom": "ipad",
"filename" : "icon-only-20.png",
"scale": "1x"
},
{
"size" : "20x20",
"idiom": "ipad",
"filename" : "[email protected]",
"scale": "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "icon-only-29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "icon-only-40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "icon-only-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "icon-only-1024.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
I've created them using a third-party tool, mentioned here: https://stackoverflow.com/a/45122603/1238150, called "Icon Set Creator."
Info.plist
My Info.plist
looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>de</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_BUNDLE_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CAPACITOR_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDarkContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true />
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Add your description for requiring permission here</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Add your description for requiring permission here</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Add your description for requiring permission here</string>
</dict>
</plist>
project.pbxproj
This file contains references to the four schemes I've created development
, staging
, beta
and production
. Each of them has at least the following build setting applied:
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
...
INFOPLIST_FILE = App/Info.plist;
...
}
Here's the full config of the staging environment used in the case I describe:
F8AD9F41291D56E00xxxxxx /* Staging */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Staging;
};
F8AD9F42291D56E000xxxxxx /* Staging */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 088FDA9807EE2E8C50xxxxxx /* Pods-App.staging.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(CAPACITOR_PROJECT_VERSION)";
DEVELOPMENT_TEAM = xxxxx;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = "$(CAPACITOR_VERSION_NAME)";
PRODUCT_BUNDLE_IDENTIFIER = "${CAPACITOR_APP_ID}";
PRODUCT_BUNDLE_NAME = "${CAPACITOR_APP_NAME}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore xx.xx.xx";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
};
App.ipa
I even investigated the generated app file and its package content, and the Info.plist
contained CFBundleIconName
when I added it but it made no difference.
The App.ipa
I am referring to was created with fastlane
's build_ios_app
(aka gym
) action, not with XCodes GUI.
Environment
fastlane environment
### Stack
| Key | Value |
| --------------------------- | ------------------------------------------- |
| OS | 12.4 |
| Ruby | 3.1.2 |
| Bundler? | false |
| Git | git version 2.38.0 |
| Installation Source | ~/.asdf/installs/ruby/3.1.2/bin/fastlane |
| Host | macOS 12.4 (21F79) |
| Ruby Lib Dir | ~/.asdf/installs/ruby/3.1.2/lib |
| OpenSSL Version | OpenSSL 3.0.5 5 Jul 2022 |
| Is contained | false |
| Is homebrew | false |
| Is installed via Fabric.app | false |
| Xcode Path | /Applications/Xcode.app/Contents/Developer/ |
| Xcode Version | 13.4.1
| Swift Version | 5.6.1 |
This is the content of my Fastfile
# Inspired by
# - https://capgo.app/blog/automatic-capacitor-ios-build-github-action/
default_platform(:ios)
config = JSON.parse(File.read('../App/capacitor.config.json'))
keychain_name = 'fastlane-certificates'
keychain_password = 'xxx'
def delete_temp_keychain(name)
# https://docs.fastlane.tools/actions/delete_keychain
delete_keychain(name:) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end
def create_temp_keychain(name, password)
# https://docs.fastlane.tools/actions/create_keychain/
create_keychain(
name:,
password:,
unlock: false,
timeout: 0
)
end
def ensure_temp_keychain(name, password)
delete_temp_keychain(name)
create_temp_keychain(name, password)
end
platform :ios do
before_all do |_lane, _options|
setup_ci if ENV['CI']
end
after_all do |_lane, _options|
delete_temp_keychain(keychain_name)
end
error do |_lane, _exception, _options|
delete_temp_keychain(keychain_name)
end
private_lane :buildNextApp do |options|
UI.message('Build App...')
yarn(
command: "build:web:#{options[:target]}",
package_path: "#{Dir.pwd}/../../../package.json"
)
UI.message('Built App')
end
private_lane :getApiKey do
app_store_connect_api_key(
key_id: 'xx',
issuer_id: 'xx',
key_filepath: "#{Dir.pwd}/xx.p8",
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
end
lane :stagingBuild do
buildNextApp(target: 'staging')
end
desc 'Push a new staging build to TestFlight'
lane :staging do
stagingBuild
ensure_temp_keychain(keychain_name, keychain_password)
match(
type: 'appstore',
app_identifier: [config['appId']],
api_key: getApiKey,
readonly: false,
fail_on_name_taken: true,
keychain_name:,
keychain_password:
)
# (https://docs.fastlane.tools/actions/build_ios_app/)
build_ios_app(
clean: true,
configuration: 'Staging',
export_method: 'app-store',
scheme: 'staging',
silent: true,
workspace: 'App.xcworkspace'
)
upload_to_testflight(
app_identifier: config['appId'],
ipa: "#{Dir.pwd}/../App.ipa",
skip_waiting_for_build_processing: true,
expire_previous_builds: true,
skip_submission: true,
submit_beta_review: false,
distribute_external: false,
notify_external_testers: false
)
end
end
And the one from my AppFile
require 'json'
config = JSON.parse(File.read('../App/capacitor.config.json'))
app_identifier(config['appId'])
# apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username
itc_team_id('xxx') # App Store Connect Team ID (https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail)
team_id('xxx') # Developer Portal Team ID (https://developer.apple.com/account/#MembershipDetailsCard)
I spent hours now and have no clue what's wrong...
After adding the setting below the upload works, but this seems to to obfuscate the error instead of resolving it...
ENV['ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD'] = 'true'
This workaround is mentioned in several other not-related issues reported by others in the fastlane GitHub repository:
- [pilot] fails to upload build to TestFlight using api key after iTMSTransporter auto updated to version 3.0.0 with
An exception has occurred: issuerId is required
error · Issue #20741 · fastlane/fastlane - Fastlane error while distributing App Through TestFlight · Issue #20773 · fastlane/fastlane
- ERROR ITMS-90186 when using upload_to_testflight · Issue #20807 · fastlane/fastlane
- App Store Connect transporter transfer failed · Issue #20879 · fastlane/fastlane
To me applying this workaround seems risky and flaky over time... That is why I am still looking for a valid and reliable solution to my issue.
If you read this far - thank you! I hope you read this far because you either have a solution or you are in the same boat as I am and hope we can find a solution together! :D