mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Use Xcode build setting PRODUCT_NAME to find app and archive paths (#140242)
1. Instead of getting the `FULL_PRODUCT_NAME` Xcode build setting (`Runner.app`) instead use `PRODUCT_NAME` since most places really want the product name, and the extension stripping wasn't correct when the name contained periods. 2. Don't instruct the user to open the `xcarchive` in Xcode if it doesn't exist. Fixes https://github.com/flutter/flutter/issues/140212
This commit is contained in:
parent
ebe53d570a
commit
f33ffc00ea
@ -517,7 +517,14 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
|
||||
|
||||
globals.printError('Encountered error while creating the IPA:');
|
||||
globals.printError(errorMessage.toString());
|
||||
globals.printError('Try distributing the app in Xcode: "open $absoluteArchivePath"');
|
||||
|
||||
final FileSystemEntityType type = globals.fs.typeSync(absoluteArchivePath);
|
||||
globals.printError('Try distributing the app in Xcode:');
|
||||
if (type == FileSystemEntityType.notFound) {
|
||||
globals.printError('open ios/Runner.xcworkspace', indent: 2);
|
||||
} else {
|
||||
globals.printError('open $absoluteArchivePath', indent: 2);
|
||||
}
|
||||
|
||||
// Even though the IPA step didn't succeed, the xcarchive did.
|
||||
// Still count this as success since the user has been instructed about how to
|
||||
|
||||
@ -108,25 +108,25 @@ abstract class IOSApp extends ApplicationPackage {
|
||||
}
|
||||
|
||||
class BuildableIOSApp extends IOSApp {
|
||||
BuildableIOSApp(this.project, String projectBundleId, String? hostAppBundleName)
|
||||
: _hostAppBundleName = hostAppBundleName,
|
||||
BuildableIOSApp(this.project, String projectBundleId, String? productName)
|
||||
: _appProductName = productName,
|
||||
super(projectBundleId: projectBundleId);
|
||||
|
||||
static Future<BuildableIOSApp?> fromProject(IosProject project, BuildInfo? buildInfo) async {
|
||||
final String? hostAppBundleName = await project.hostAppBundleName(buildInfo);
|
||||
final String? productName = await project.productName(buildInfo);
|
||||
final String? projectBundleId = await project.productBundleIdentifier(buildInfo);
|
||||
if (projectBundleId != null) {
|
||||
return BuildableIOSApp(project, projectBundleId, hostAppBundleName);
|
||||
return BuildableIOSApp(project, projectBundleId, productName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final IosProject project;
|
||||
|
||||
final String? _hostAppBundleName;
|
||||
final String? _appProductName;
|
||||
|
||||
@override
|
||||
String? get name => _hostAppBundleName;
|
||||
String? get name => _appProductName;
|
||||
|
||||
@override
|
||||
String get simulatorBundlePath => _buildAppPath('iphonesimulator');
|
||||
@ -141,16 +141,15 @@ class BuildableIOSApp extends IOSApp {
|
||||
// not a top-level output directory.
|
||||
// Specifying `build/ios/archive/Runner` will result in `build/ios/archive/Runner.xcarchive`.
|
||||
String get archiveBundlePath => globals.fs.path.join(getIosBuildDirectory(), 'archive',
|
||||
_hostAppBundleName == null ? 'Runner' : globals.fs.path.withoutExtension(_hostAppBundleName));
|
||||
_appProductName ?? 'Runner');
|
||||
|
||||
// The output xcarchive bundle path `build/ios/archive/Runner.xcarchive`.
|
||||
String get archiveBundleOutputPath =>
|
||||
globals.fs.path.setExtension(archiveBundlePath, '.xcarchive');
|
||||
String get archiveBundleOutputPath => '$archiveBundlePath.xcarchive';
|
||||
|
||||
String get builtInfoPlistPathAfterArchive => globals.fs.path.join(archiveBundleOutputPath,
|
||||
'Products',
|
||||
'Applications',
|
||||
_hostAppBundleName ?? 'Runner.app',
|
||||
_appProductName != null ? '$_appProductName.app' : 'Runner.app',
|
||||
'Info.plist');
|
||||
|
||||
String get projectAppIconDirName => _projectImageAssetDirName(_appIconAsset);
|
||||
@ -173,7 +172,7 @@ class BuildableIOSApp extends IOSApp {
|
||||
globals.fs.path.join(getIosBuildDirectory(), 'ipa');
|
||||
|
||||
String _buildAppPath(String type) {
|
||||
return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName);
|
||||
return globals.fs.path.join(getIosBuildDirectory(), type, '$_appProductName.app');
|
||||
}
|
||||
|
||||
String _projectImageAssetDirName(String asset)
|
||||
|
||||
@ -174,7 +174,7 @@ class IosProject extends XcodeBasedProject {
|
||||
static const String kProductBundleIdKey = 'PRODUCT_BUNDLE_IDENTIFIER';
|
||||
static const String kTeamIdKey = 'DEVELOPMENT_TEAM';
|
||||
static const String kEntitlementFilePathKey = 'CODE_SIGN_ENTITLEMENTS';
|
||||
static const String kHostAppBundleNameKey = 'FULL_PRODUCT_NAME';
|
||||
static const String kProductNameKey = 'PRODUCT_NAME';
|
||||
|
||||
static final RegExp _productBundleIdPattern = RegExp('^\\s*$kProductBundleIdKey\\s*=\\s*(["\']?)(.*?)\\1;\\s*\$');
|
||||
static const String _kProductBundleIdVariable = '\$($kProductBundleIdKey)';
|
||||
@ -397,16 +397,16 @@ class IosProject extends XcodeBasedProject {
|
||||
return const <String>[];
|
||||
}
|
||||
|
||||
/// The bundle name of the host app, `My App.app`.
|
||||
Future<String?> hostAppBundleName(BuildInfo? buildInfo) async {
|
||||
/// The product name of the app, `My App`.
|
||||
Future<String?> productName(BuildInfo? buildInfo) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
|
||||
return _productName ??= await _parseProductName(buildInfo);
|
||||
}
|
||||
String? _hostAppBundleName;
|
||||
String? _productName;
|
||||
|
||||
Future<String> _parseHostAppBundleName(BuildInfo? buildInfo) async {
|
||||
Future<String> _parseProductName(BuildInfo? buildInfo) async {
|
||||
// The product name and bundle name are derived from the display name, which the user
|
||||
// is instructed to change in Xcode as part of deploying to the App Store.
|
||||
// https://flutter.dev/to/xcode-name-config
|
||||
@ -415,13 +415,13 @@ class IosProject extends XcodeBasedProject {
|
||||
if (globals.xcodeProjectInterpreter?.isInstalled ?? false) {
|
||||
final Map<String, String>? xcodeBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (xcodeBuildSettings != null) {
|
||||
productName = xcodeBuildSettings[kHostAppBundleNameKey];
|
||||
productName = xcodeBuildSettings[kProductNameKey];
|
||||
}
|
||||
}
|
||||
if (productName == null) {
|
||||
globals.printTrace('$kHostAppBundleNameKey not present, defaulting to $hostAppProjectName');
|
||||
globals.printTrace('$kProductNameKey not present, defaulting to $hostAppProjectName');
|
||||
}
|
||||
return productName ?? '${XcodeBasedProject._defaultHostAppName}.app';
|
||||
return productName ?? XcodeBasedProject._defaultHostAppName;
|
||||
}
|
||||
|
||||
/// The build settings for the host app of this project, as a detached map.
|
||||
|
||||
@ -654,14 +654,18 @@ void main() {
|
||||
]);
|
||||
createMinimalMockProjectFiles();
|
||||
|
||||
fileSystem.directory('build/ios/archive/Runner.xcarchive').createSync(recursive: true);
|
||||
|
||||
await createTestCommandRunner(command).run(
|
||||
const <String>['build', 'ipa', '--no-pub']
|
||||
);
|
||||
|
||||
expect(logger.statusText, contains('build/ios/archive/Runner.xcarchive'));
|
||||
expect(logger.statusText, contains('Built build/ios/archive/Runner.xcarchive'));
|
||||
expect(logger.statusText, contains('Building App Store IPA'));
|
||||
expect(logger.errorText, contains('Encountered error while creating the IPA:'));
|
||||
expect(logger.errorText, contains('error: exportArchive: "Runner.app" requires a provisioning profile.'));
|
||||
expect(logger.errorText, contains('Try distributing the app in Xcode:'));
|
||||
expect(logger.errorText, contains('open /build/ios/archive/Runner.xcarchive'));
|
||||
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
@ -1231,7 +1235,7 @@ void main() {
|
||||
});
|
||||
|
||||
|
||||
testUsingContext('Extra error message for provision profile issue in xcresulb bundle.', () async {
|
||||
testUsingContext('Extra error message for provision profile issue in xcresult bundle.', () async {
|
||||
final BuildCommand command = BuildCommand(
|
||||
artifacts: artifacts,
|
||||
androidSdk: FakeAndroidSdk(),
|
||||
|
||||
@ -463,6 +463,19 @@ void main() {
|
||||
expect(iosApp, null);
|
||||
}, overrides: overrides);
|
||||
|
||||
testUsingContext('handles project paths with periods in app name', () async {
|
||||
final BuildableIOSApp iosApp = BuildableIOSApp(
|
||||
IosProject.fromFlutter(FlutterProject.fromDirectory(globals.fs.currentDirectory)),
|
||||
'com.foo.bar',
|
||||
'Name.With.Dots',
|
||||
);
|
||||
expect(iosApp.name, 'Name.With.Dots');
|
||||
expect(iosApp.archiveBundleOutputPath, 'build/ios/archive/Name.With.Dots.xcarchive');
|
||||
expect(iosApp.deviceBundlePath, 'build/ios/iphoneos/Name.With.Dots.app');
|
||||
expect(iosApp.simulatorBundlePath, 'build/ios/iphonesimulator/Name.With.Dots.app');
|
||||
expect(iosApp.builtInfoPlistPathAfterArchive, 'build/ios/archive/Name.With.Dots.xcarchive/Products/Applications/Name.With.Dots.app/Info.plist');
|
||||
}, overrides: overrides);
|
||||
|
||||
testUsingContext('returns project app icon dirname', () async {
|
||||
final BuildableIOSApp iosApp = BuildableIOSApp(
|
||||
IosProject.fromFlutter(FlutterProject.fromDirectory(globals.fs.currentDirectory)),
|
||||
|
||||
@ -127,7 +127,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
|
||||
processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject)));
|
||||
processManager.addCommand(const FakeCommand(command: kRunReleaseArgs));
|
||||
@ -171,7 +171,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
buildableIOSApp,
|
||||
@ -199,7 +199,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject)));
|
||||
@ -257,7 +257,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
|
||||
processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject)));
|
||||
// The first xcrun call should fail with a
|
||||
@ -346,7 +346,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -380,7 +380,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -414,7 +414,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -447,7 +447,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -496,7 +496,7 @@ void main() {
|
||||
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
|
||||
@ -571,7 +571,7 @@ void main() {
|
||||
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
|
||||
@ -639,7 +639,7 @@ void main() {
|
||||
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
|
||||
@ -702,7 +702,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -741,7 +741,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem, createWorkspace: false);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
@ -780,7 +780,7 @@ void main() {
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
|
||||
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
|
||||
|
||||
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
|
||||
|
||||
@ -797,7 +797,7 @@ class FakeIosProject extends Fake implements IosProject {
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
Future<String> hostAppBundleName(BuildInfo? buildInfo) async => 'UnitTestRunner.app';
|
||||
Future<String> productName(BuildInfo? buildInfo) async => 'UnitTestRunner';
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj');
|
||||
|
||||
@ -1332,7 +1332,7 @@ class FakeIosProject extends Fake implements IosProject {
|
||||
Future<String> productBundleIdentifier(BuildInfo? buildInfo) async => 'com.example.test';
|
||||
|
||||
@override
|
||||
Future<String> hostAppBundleName(BuildInfo? buildInfo) async => 'My Super Awesome App.app';
|
||||
Future<String> productName(BuildInfo? buildInfo) async => 'My Super Awesome App';
|
||||
}
|
||||
|
||||
class FakeSimControl extends Fake implements SimControl {
|
||||
|
||||
@ -1188,9 +1188,9 @@ plugins {
|
||||
mockXcodeProjectInterpreter = FakeXcodeProjectInterpreter();
|
||||
});
|
||||
|
||||
testUsingContext('app product name defaults to Runner.app', () async {
|
||||
testUsingContext('app product name defaults to Runner', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
expect(await project.ios.hostAppBundleName(null), 'Runner.app');
|
||||
expect(await project.ios.productName(null), 'Runner');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
@ -1202,11 +1202,11 @@ plugins {
|
||||
project.ios.xcodeProject.createSync();
|
||||
const XcodeProjectBuildContext buildContext = XcodeProjectBuildContext(scheme: 'Runner');
|
||||
mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{
|
||||
'FULL_PRODUCT_NAME': 'My App.app',
|
||||
'PRODUCT_NAME': 'My App',
|
||||
};
|
||||
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
||||
|
||||
expect(await project.ios.hostAppBundleName(null), 'My App.app');
|
||||
expect(await project.ios.productName(null), 'My App');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user