Fix extraction of product bundle ID for iOS projects (#21252)
This commit is contained in:
parent
f999f1447a
commit
6cc8008283
@ -34,7 +34,7 @@ abstract class ApplicationPackage {
|
|||||||
File get packagesFile => null;
|
File get packagesFile => null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => displayName;
|
String toString() => displayName ?? id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidApk extends ApplicationPackage {
|
class AndroidApk extends ApplicationPackage {
|
||||||
|
@ -78,7 +78,7 @@ class BuildIOSCommand extends BuildSubCommand {
|
|||||||
final String logTarget = forSimulator ? 'simulator' : 'device';
|
final String logTarget = forSimulator ? 'simulator' : 'device';
|
||||||
|
|
||||||
final String typeName = artifacts.getEngineType(TargetPlatform.ios, buildInfo.mode);
|
final String typeName = artifacts.getEngineType(TargetPlatform.ios, buildInfo.mode);
|
||||||
printStatus('Building ${app.toString()} for $logTarget ($typeName)...');
|
printStatus('Building $app for $logTarget ($typeName)...');
|
||||||
final XcodeBuildResult result = await buildXcodeProject(
|
final XcodeBuildResult result = await buildXcodeProject(
|
||||||
app: app,
|
app: app,
|
||||||
buildInfo: buildInfo,
|
buildInfo: buildInfo,
|
||||||
|
@ -16,7 +16,9 @@ String getValueFromFile(String plistFilePath, String key) {
|
|||||||
// Don't use PlistBuddy since that is not guaranteed to be installed.
|
// Don't use PlistBuddy since that is not guaranteed to be installed.
|
||||||
// 'defaults' requires the path to be absolute and without the 'plist'
|
// 'defaults' requires the path to be absolute and without the 'plist'
|
||||||
// extension.
|
// extension.
|
||||||
|
const String executable = '/usr/bin/defaults';
|
||||||
|
if (!fs.isFileSync(executable))
|
||||||
|
return null;
|
||||||
if (!fs.isFileSync(plistFilePath))
|
if (!fs.isFileSync(plistFilePath))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ String getValueFromFile(String plistFilePath, String key) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final String value = runCheckedSync(<String>[
|
final String value = runCheckedSync(<String>[
|
||||||
'/usr/bin/defaults', 'read', normalizedPlistPath, key
|
executable, 'read', normalizedPlistPath, key
|
||||||
]);
|
]);
|
||||||
return value.isEmpty ? null : value;
|
return value.isEmpty ? null : value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -20,7 +20,7 @@ import '../globals.dart';
|
|||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
|
|
||||||
final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
|
final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
|
||||||
final RegExp _varExpr = new RegExp(r'\$\((.*)\)');
|
final RegExp _varExpr = new RegExp(r'\$\(([^)]*)\)');
|
||||||
|
|
||||||
String flutterFrameworkDir(BuildMode mode) {
|
String flutterFrameworkDir(BuildMode mode) {
|
||||||
return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode)));
|
return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode)));
|
||||||
|
@ -13,6 +13,8 @@ import 'build_info.dart';
|
|||||||
import 'bundle.dart' as bundle;
|
import 'bundle.dart' as bundle;
|
||||||
import 'cache.dart';
|
import 'cache.dart';
|
||||||
import 'flutter_manifest.dart';
|
import 'flutter_manifest.dart';
|
||||||
|
import 'ios/ios_workflow.dart';
|
||||||
|
import 'ios/plist_utils.dart' as plist;
|
||||||
import 'ios/xcodeproj.dart' as xcode;
|
import 'ios/xcodeproj.dart' as xcode;
|
||||||
import 'plugins.dart';
|
import 'plugins.dart';
|
||||||
import 'template.dart';
|
import 'template.dart';
|
||||||
@ -147,6 +149,7 @@ class FlutterProject {
|
|||||||
/// Flutter applications and the `.ios/` sub-folder of Flutter modules.
|
/// Flutter applications and the `.ios/` sub-folder of Flutter modules.
|
||||||
class IosProject {
|
class IosProject {
|
||||||
static final RegExp _productBundleIdPattern = new RegExp(r'^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(.*);\s*$');
|
static final RegExp _productBundleIdPattern = new RegExp(r'^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(.*);\s*$');
|
||||||
|
static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)';
|
||||||
static const String _hostAppBundleName = 'Runner';
|
static const String _hostAppBundleName = 'Runner';
|
||||||
|
|
||||||
IosProject._(this.parent);
|
IosProject._(this.parent);
|
||||||
@ -174,22 +177,47 @@ class IosProject {
|
|||||||
/// The 'Manifest.lock'.
|
/// The 'Manifest.lock'.
|
||||||
File get podManifestLock => directory.childDirectory('Pods').childFile('Manifest.lock');
|
File get podManifestLock => directory.childDirectory('Pods').childFile('Manifest.lock');
|
||||||
|
|
||||||
|
/// The 'Info.plist' file of the host app.
|
||||||
|
File get hostInfoPlist => directory.childDirectory(_hostAppBundleName).childFile('Info.plist');
|
||||||
|
|
||||||
/// '.xcodeproj' folder of the host app.
|
/// '.xcodeproj' folder of the host app.
|
||||||
Directory get xcodeProject => directory.childDirectory('$_hostAppBundleName.xcodeproj');
|
Directory get xcodeProject => directory.childDirectory('$_hostAppBundleName.xcodeproj');
|
||||||
|
|
||||||
/// The '.pbxproj' file of the host app.
|
/// The '.pbxproj' file of the host app.
|
||||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||||
|
|
||||||
/// The product bundle identifier of the host app.
|
/// The product bundle identifier of the host app, or null if not set or if
|
||||||
|
/// iOS tooling needed to read it is not installed.
|
||||||
String get productBundleIdentifier {
|
String get productBundleIdentifier {
|
||||||
return _firstMatchInFile(xcodeProjectInfoFile, _productBundleIdPattern)?.group(1);
|
final String fromPlist = iosWorkflow.getPlistValueFromFile(
|
||||||
|
hostInfoPlist.path,
|
||||||
|
plist.kCFBundleIdentifierKey,
|
||||||
|
);
|
||||||
|
if (fromPlist != null && !fromPlist.contains('\$')) {
|
||||||
|
// Info.plist has no build variables in product bundle ID.
|
||||||
|
return fromPlist;
|
||||||
|
}
|
||||||
|
final String fromPbxproj = _firstMatchInFile(xcodeProjectInfoFile, _productBundleIdPattern)?.group(1);
|
||||||
|
if (fromPbxproj != null && (fromPlist == null || fromPlist == _productBundleIdVariable)) {
|
||||||
|
// Common case. Avoids parsing build settings.
|
||||||
|
return fromPbxproj;
|
||||||
|
}
|
||||||
|
if (fromPlist != null && xcode.xcodeProjectInterpreter.isInstalled) {
|
||||||
|
// General case: perform variable substitution using build settings.
|
||||||
|
return xcode.substituteXcodeVariables(fromPlist, buildSettings);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True, if the host app project is using Swift.
|
/// True, if the host app project is using Swift.
|
||||||
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION');
|
bool get isSwift => buildSettings?.containsKey('SWIFT_VERSION');
|
||||||
|
|
||||||
/// The build settings for the host app of this project, as a detached map.
|
/// The build settings for the host app of this project, as a detached map.
|
||||||
|
///
|
||||||
|
/// Returns null, if iOS tooling is unavailable.
|
||||||
Map<String, String> get buildSettings {
|
Map<String, String> get buildSettings {
|
||||||
|
if (!xcode.xcodeProjectInterpreter.isInstalled)
|
||||||
|
return null;
|
||||||
return xcode.xcodeProjectInterpreter.getBuildSettings(xcodeProject.path, _hostAppBundleName);
|
return xcode.xcodeProjectInterpreter.getBuildSettings(xcodeProject.path, _hostAppBundleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,10 +9,13 @@ import 'package:flutter_tools/src/base/context.dart';
|
|||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
import 'package:flutter_tools/src/cache.dart';
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||||
|
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||||
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||||
import 'package:flutter_tools/src/project.dart';
|
import 'package:flutter_tools/src/project.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import 'src/common.dart';
|
import 'src/common.dart';
|
||||||
import 'src/context.dart';
|
import 'src/context.dart';
|
||||||
@ -221,6 +224,55 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('product bundle identifier', () {
|
||||||
|
MemoryFileSystem fs;
|
||||||
|
MockIOSWorkflow mockIOSWorkflow;
|
||||||
|
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
||||||
|
setUp(() {
|
||||||
|
fs = new MemoryFileSystem();
|
||||||
|
mockIOSWorkflow = new MockIOSWorkflow();
|
||||||
|
mockXcodeProjectInterpreter = new MockXcodeProjectInterpreter();
|
||||||
|
});
|
||||||
|
|
||||||
|
void testWithMocks(String description, Future<Null> testMethod()) {
|
||||||
|
testUsingContext(description, testMethod, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fs,
|
||||||
|
IOSWorkflow: () => mockIOSWorkflow,
|
||||||
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testWithMocks('null, if no pbxproj or plist entries', () async {
|
||||||
|
final FlutterProject project = await someProject();
|
||||||
|
expect(project.ios.productBundleIdentifier, isNull);
|
||||||
|
});
|
||||||
|
testWithMocks('from pbxproj file, if no plist', () async {
|
||||||
|
final FlutterProject project = await someProject();
|
||||||
|
addIosWithBundleId(project.directory, 'io.flutter.someProject');
|
||||||
|
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject');
|
||||||
|
});
|
||||||
|
testWithMocks('from plist, if no variables', () async {
|
||||||
|
final FlutterProject project = await someProject();
|
||||||
|
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('io.flutter.someProject');
|
||||||
|
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject');
|
||||||
|
});
|
||||||
|
testWithMocks('from pbxproj and plist, if default variable', () async {
|
||||||
|
final FlutterProject project = await someProject();
|
||||||
|
addIosWithBundleId(project.directory, 'io.flutter.someProject');
|
||||||
|
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER)');
|
||||||
|
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject');
|
||||||
|
});
|
||||||
|
testWithMocks('from pbxproj and plist, by substitution', () async {
|
||||||
|
final FlutterProject project = await someProject();
|
||||||
|
when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{
|
||||||
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
||||||
|
'SUFFIX': 'suffix',
|
||||||
|
});
|
||||||
|
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER).\$(SUFFIX)');
|
||||||
|
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject.suffix');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('organization names set', () {
|
group('organization names set', () {
|
||||||
testInMemory('is empty, if project not created', () async {
|
testInMemory('is empty, if project not created', () async {
|
||||||
final FlutterProject project = await someProject();
|
final FlutterProject project = await someProject();
|
||||||
@ -457,3 +509,10 @@ File androidPluginRegistrant(Directory parent) {
|
|||||||
.childDirectory('plugins')
|
.childDirectory('plugins')
|
||||||
.childFile('GeneratedPluginRegistrant.java');
|
.childFile('GeneratedPluginRegistrant.java');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockIOSWorkflow extends Mock implements IOSWorkflow {}
|
||||||
|
|
||||||
|
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {
|
||||||
|
@override
|
||||||
|
bool get isInstalled => true;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user