Unverified Commit c22ce95e authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Change from using `defaults` to `plutil` for Plist parsing (#38662)

We were using the `defaults` command-line utility to parse
Plist files, but it was never supported by Apple, and it
appears that in an upcoming OS release, it will be less likely
to work:

> WARNING: The defaults command will be changed in an upcoming
> major release to only operate on preferences domains. General
> plist manipulation utilities will be folded into a different
> command-line program.

Fixes https://github.com/flutter/flutter/issues/37701
parent 50b55022
...@@ -10,8 +10,7 @@ import '../base/platform.dart'; ...@@ -10,8 +10,7 @@ import '../base/platform.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/ios_workflow.dart'; import '../ios/plist_parser.dart';
import '../ios/plist_utils.dart' as plist;
AndroidStudio get androidStudio => context.get<AndroidStudio>(); AndroidStudio get androidStudio => context.get<AndroidStudio>();
...@@ -43,34 +42,30 @@ class AndroidStudio implements Comparable<AndroidStudio> { ...@@ -43,34 +42,30 @@ class AndroidStudio implements Comparable<AndroidStudio> {
factory AndroidStudio.fromMacOSBundle(String bundlePath) { factory AndroidStudio.fromMacOSBundle(String bundlePath) {
String studioPath = fs.path.join(bundlePath, 'Contents'); String studioPath = fs.path.join(bundlePath, 'Contents');
String plistFile = fs.path.join(studioPath, 'Info.plist'); String plistFile = fs.path.join(studioPath, 'Info.plist');
String plistValue = iosWorkflow.getPlistValueFromFile( Map<String, dynamic> plistValues = PlistParser.instance.parseFile(plistFile);
plistFile,
null,
);
final RegExp _pathsSelectorMatcher = RegExp(r'"idea.paths.selector" = "[^;]+"');
final RegExp _jetBrainsToolboxAppMatcher = RegExp(r'JetBrainsToolboxApp = "[^;]+"');
// As AndroidStudio managed by JetBrainsToolbox could have a wrapper pointing to the real Android Studio. // As AndroidStudio managed by JetBrainsToolbox could have a wrapper pointing to the real Android Studio.
// Check if we've found a JetBrainsToolbox wrapper and deal with it properly. // Check if we've found a JetBrainsToolbox wrapper and deal with it properly.
final String jetBrainsToolboxAppBundlePath = extractStudioPlistValueWithMatcher(plistValue, _jetBrainsToolboxAppMatcher); final String jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'];
if (jetBrainsToolboxAppBundlePath != null) { if (jetBrainsToolboxAppBundlePath != null) {
studioPath = fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents'); studioPath = fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents');
plistFile = fs.path.join(studioPath, 'Info.plist'); plistFile = fs.path.join(studioPath, 'Info.plist');
plistValue = iosWorkflow.getPlistValueFromFile( plistValues = PlistParser.instance.parseFile(plistFile);
plistFile,
null,
);
} }
final String versionString = iosWorkflow.getPlistValueFromFile( final String versionString = plistValues[PlistParser.kCFBundleShortVersionStringKey];
plistFile,
plist.kCFBundleShortVersionStringKey,
);
Version version; Version version;
if (versionString != null) if (versionString != null)
version = Version.parse(versionString); version = Version.parse(versionString);
final String pathsSelectorValue = extractStudioPlistValueWithMatcher(plistValue, _pathsSelectorMatcher); String pathsSelectorValue;
final Map<String, dynamic> jvmOptions = plistValues['JVMOptions'];
if (jvmOptions != null) {
final Map<String, dynamic> jvmProperties = jvmOptions['Properties'];
if (jvmProperties != null) {
pathsSelectorValue = jvmProperties['idea.paths.selector'];
}
}
final String presetPluginsPath = pathsSelectorValue == null final String presetPluginsPath = pathsSelectorValue == null
? null ? null
: fs.path.join(homeDirPath, 'Library', 'Application Support', '$pathsSelectorValue'); : fs.path.join(homeDirPath, 'Library', 'Application Support', '$pathsSelectorValue');
......
...@@ -19,8 +19,7 @@ import 'base/user_messages.dart'; ...@@ -19,8 +19,7 @@ import 'base/user_messages.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'fuchsia/application_package.dart'; import 'fuchsia/application_package.dart';
import 'globals.dart'; import 'globals.dart';
import 'ios/ios_workflow.dart'; import 'ios/plist_parser.dart';
import 'ios/plist_utils.dart' as plist;
import 'linux/application_package.dart'; import 'linux/application_package.dart';
import 'macos/application_package.dart'; import 'macos/application_package.dart';
import 'project.dart'; import 'project.dart';
...@@ -309,9 +308,9 @@ abstract class IOSApp extends ApplicationPackage { ...@@ -309,9 +308,9 @@ abstract class IOSApp extends ApplicationPackage {
printError('Invalid prebuilt iOS app. Does not contain Info.plist.'); printError('Invalid prebuilt iOS app. Does not contain Info.plist.');
return null; return null;
} }
final String id = iosWorkflow.getPlistValueFromFile( final String id = PlistParser.instance.getValueFromFile(
plistPath, plistPath,
plist.kCFBundleIdentifierKey, PlistParser.kCFBundleIdentifierKey,
); );
if (id == null) { if (id == null) {
printError('Invalid prebuilt iOS app. Info.plist does not contain bundle identifier'); printError('Invalid prebuilt iOS app. Info.plist does not contain bundle identifier');
......
...@@ -157,3 +157,13 @@ bool isOlderThanReference({ @required FileSystemEntity entity, @required File re ...@@ -157,3 +157,13 @@ bool isOlderThanReference({ @required FileSystemEntity entity, @required File re
return referenceFile.existsSync() return referenceFile.existsSync()
&& referenceFile.lastModifiedSync().isAfter(entity.statSync().modified); && referenceFile.lastModifiedSync().isAfter(entity.statSync().modified);
} }
/// Exception indicating that a file that was expected to exist was not found.
class FileNotFoundException implements IOException {
const FileNotFoundException(this.path);
final String path;
@override
String toString() => 'File not found: $path';
}
...@@ -15,7 +15,7 @@ import '../base/version.dart'; ...@@ -15,7 +15,7 @@ import '../base/version.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/ios_workflow.dart'; import '../ios/plist_parser.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
...@@ -222,7 +222,7 @@ Future<void> validateBitcode() async { ...@@ -222,7 +222,7 @@ Future<void> validateBitcode() async {
} }
final RunResult clangResult = await xcode.clang(<String>['--version']); final RunResult clangResult = await xcode.clang(<String>['--version']);
final String clangVersion = clangResult.stdout.split('\n').first; final String clangVersion = clangResult.stdout.split('\n').first;
final String engineClangVersion = iosWorkflow.getPlistValueFromFile( final String engineClangVersion = PlistParser.instance.getValueFromFile(
fs.path.join(flutterFrameworkPath, 'Info.plist'), fs.path.join(flutterFrameworkPath, 'Info.plist'),
'ClangVersion', 'ClangVersion',
); );
......
...@@ -24,7 +24,7 @@ import 'fuchsia/fuchsia_workflow.dart'; ...@@ -24,7 +24,7 @@ import 'fuchsia/fuchsia_workflow.dart';
import 'globals.dart'; import 'globals.dart';
import 'intellij/intellij.dart'; import 'intellij/intellij.dart';
import 'ios/ios_workflow.dart'; import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart'; import 'ios/plist_parser.dart';
import 'linux/linux_doctor.dart'; import 'linux/linux_doctor.dart';
import 'linux/linux_workflow.dart'; import 'linux/linux_workflow.dart';
import 'macos/cocoapods_validator.dart'; import 'macos/cocoapods_validator.dart';
...@@ -731,9 +731,9 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -731,9 +731,9 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
String get version { String get version {
if (_version == null) { if (_version == null) {
final String plistFile = fs.path.join(installPath, 'Contents', 'Info.plist'); final String plistFile = fs.path.join(installPath, 'Contents', 'Info.plist');
_version = iosWorkflow.getPlistValueFromFile( _version = PlistParser.instance.getValueFromFile(
plistFile, plistFile,
kCFBundleShortVersionStringKey, PlistParser.kCFBundleShortVersionStringKey,
) ?? 'unknown'; ) ?? 'unknown';
} }
return _version; return _version;
......
...@@ -6,7 +6,6 @@ import '../base/context.dart'; ...@@ -6,7 +6,6 @@ import '../base/context.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../doctor.dart'; import '../doctor.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
import 'plist_utils.dart' as plist;
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>(); IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
...@@ -27,8 +26,4 @@ class IOSWorkflow implements Workflow { ...@@ -27,8 +26,4 @@ class IOSWorkflow implements Workflow {
@override @override
bool get canListEmulators => false; bool get canListEmulators => false;
String getPlistValueFromFile(String path, String key) {
return plist.getValueFromFile(path, key);
}
} }
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/process.dart';
import '../convert.dart';
import '../globals.dart';
class PlistParser {
const PlistParser();
static const String kCFBundleIdentifierKey = 'CFBundleIdentifier';
static const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString';
static const String kCFBundleExecutable = 'CFBundleExecutable';
static PlistParser get instance => context.get<PlistParser>() ?? const PlistParser();
/// Parses the plist file located at [plistFilePath] and returns the
/// associated map of key/value property list pairs.
///
/// If [plistFilePath] points to a non-existent file or a file that's not a
/// valid property list file, this will return an empty map.
///
/// The [plistFilePath] argument must not be null.
Map<String, dynamic> parseFile(String plistFilePath) {
assert(plistFilePath != null);
const String executable = '/usr/bin/plutil';
if (!fs.isFileSync(executable))
throw const FileNotFoundException(executable);
if (!fs.isFileSync(plistFilePath))
return const <String, dynamic>{};
final String normalizedPlistPath = fs.path.absolute(plistFilePath);
try {
final List<String> args = <String>[
executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
];
final String jsonContent = runCheckedSync(args);
return json.decode(jsonContent);
} catch (error) {
printTrace('$error');
return const <String, dynamic>{};
}
}
/// Parses the Plist file located at [plistFilePath] and returns the value
/// that's associated with the specified [key] within the property list.
///
/// If [plistFilePath] points to a non-existent file or a file that's not a
/// valid property list file, this will return null.
///
/// If [key] is not found in the property list, this will return null.
///
/// The [plistFilePath] and [key] arguments must not be null.
String getValueFromFile(String plistFilePath, String key) {
assert(key != null);
final Map<String, dynamic> parsed = parseFile(plistFilePath);
return parsed[key];
}
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/file_system.dart';
import '../base/process.dart';
const String kCFBundleIdentifierKey = 'CFBundleIdentifier';
const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString';
const String kCFBundleExecutable = 'CFBundleExecutable';
// Prefer using [iosWorkflow.getPlistValueFromFile] to enable mocking.
String getValueFromFile(String plistFilePath, String key) {
// TODO(chinmaygarde): For now, we only need to read from plist files on a mac
// host. If this changes, we will need our own Dart plist reader.
// Don't use PlistBuddy since that is not guaranteed to be installed.
// 'defaults' requires the path to be absolute and without the 'plist'
// extension.
const String executable = '/usr/bin/defaults';
if (!fs.isFileSync(executable))
return null;
if (!fs.isFileSync(plistFilePath))
return null;
final String normalizedPlistPath = fs.path.withoutExtension(fs.path.absolute(plistFilePath));
try {
final List<String> args = <String>[
executable, 'read', normalizedPlistPath,
];
if (key != null && key.isNotEmpty) {
args.add(key);
}
final String value = runCheckedSync(args);
return value.isEmpty ? null : value;
} catch (error) {
return null;
}
}
...@@ -24,7 +24,7 @@ import '../project.dart'; ...@@ -24,7 +24,7 @@ import '../project.dart';
import '../protocol_discovery.dart'; import '../protocol_discovery.dart';
import 'ios_workflow.dart'; import 'ios_workflow.dart';
import 'mac.dart'; import 'mac.dart';
import 'plist_utils.dart'; import 'plist_parser.dart';
const String _xcrunPath = '/usr/bin/xcrun'; const String _xcrunPath = '/usr/bin/xcrun';
const String iosSimulatorId = 'apple_ios_simulator'; const String iosSimulatorId = 'apple_ios_simulator';
...@@ -379,7 +379,7 @@ class IOSSimulator extends Device { ...@@ -379,7 +379,7 @@ class IOSSimulator extends Device {
// parsing the xcodeproj or configuration files. // parsing the xcodeproj or configuration files.
// See https://github.com/flutter/flutter/issues/31037 for more information. // See https://github.com/flutter/flutter/issues/31037 for more information.
final String plistPath = fs.path.join(package.simulatorBundlePath, 'Info.plist'); final String plistPath = fs.path.join(package.simulatorBundlePath, 'Info.plist');
final String bundleIdentifier = iosWorkflow.getPlistValueFromFile(plistPath, kCFBundleIdentifierKey); final String bundleIdentifier = PlistParser.instance.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
await SimControl.instance.launch(id, bundleIdentifier, args); await SimControl.instance.launch(id, bundleIdentifier, args);
} catch (error) { } catch (error) {
......
...@@ -8,7 +8,7 @@ import '../application_package.dart'; ...@@ -8,7 +8,7 @@ import '../application_package.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/plist_utils.dart' as plist; import '../ios/plist_parser.dart';
import '../project.dart'; import '../project.dart';
/// Tests whether a [FileSystemEntity] is an macOS bundle directory /// Tests whether a [FileSystemEntity] is an macOS bundle directory
...@@ -65,8 +65,9 @@ abstract class MacOSApp extends ApplicationPackage { ...@@ -65,8 +65,9 @@ abstract class MacOSApp extends ApplicationPackage {
printError('Invalid prebuilt macOS app. Does not contain Info.plist.'); printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
return null; return null;
} }
final String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey); final Map<String, dynamic> propertyValues = PlistParser.instance.parseFile(plistPath);
final String executableName = plist.getValueFromFile(plistPath, plist.kCFBundleExecutable); final String id = propertyValues[PlistParser.kCFBundleIdentifierKey];
final String executableName = propertyValues[PlistParser.kCFBundleExecutable];
if (id == null) { if (id == null) {
printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier'); printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier');
return null; return null;
......
...@@ -17,8 +17,7 @@ import 'cache.dart'; ...@@ -17,8 +17,7 @@ import 'cache.dart';
import 'features.dart'; import 'features.dart';
import 'flutter_manifest.dart'; import 'flutter_manifest.dart';
import 'globals.dart'; import 'globals.dart';
import 'ios/ios_workflow.dart'; import 'ios/plist_parser.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';
...@@ -361,10 +360,15 @@ class IosProject implements XcodeBasedProject { ...@@ -361,10 +360,15 @@ class IosProject implements XcodeBasedProject {
/// The product bundle identifier of the host app, or null if not set or if /// The product bundle identifier of the host app, or null if not set or if
/// iOS tooling needed to read it is not installed. /// iOS tooling needed to read it is not installed.
String get productBundleIdentifier { String get productBundleIdentifier {
final String fromPlist = iosWorkflow.getPlistValueFromFile( String fromPlist;
hostInfoPlist.path, try {
plist.kCFBundleIdentifierKey, fromPlist = PlistParser.instance.getValueFromFile(
); hostInfoPlist.path,
PlistParser.kCFBundleIdentifierKey,
);
} on FileNotFoundException {
// iOS tooling not found; likely not running OSX; let [fromPlist] be null
}
if (fromPlist != null && !fromPlist.contains('\$')) { if (fromPlist != null && !fromPlist.contains('\$')) {
// Info.plist has no build variables in product bundle ID. // Info.plist has no build variables in product bundle ID.
return fromPlist; return fromPlist;
......
...@@ -6,7 +6,7 @@ import 'package:file/memory.dart'; ...@@ -6,7 +6,7 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/android_studio.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -15,47 +15,19 @@ import '../../src/context.dart'; ...@@ -15,47 +15,19 @@ import '../../src/context.dart';
const String homeLinux = '/home/me'; const String homeLinux = '/home/me';
const String homeMac = '/Users/me'; const String homeMac = '/Users/me';
const String macStudioInfoPlistValue = const Map<String, dynamic> macStudioInfoPlist = <String, dynamic>{
''' 'CFBundleGetInfoString': 'Android Studio 3.3, build AI-182.5107.16.33.5199772. Copyright JetBrains s.r.o., (c) 2000-2018',
<?xml version="1.0" encoding="UTF-8"?> 'CFBundleShortVersionString': '3.3',
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 'CFBundleVersion': 'AI-182.5107.16.33.5199772',
<plist version="1.0"> 'JVMOptions': <String, dynamic>{
<dict> 'Properties': <String, dynamic>{
<key>CFBundleGetInfoString</key> 'idea.paths.selector': 'AndroidStudio3.3',
<string>Android Studio 3.3, build AI-182.5107.16.33.5199772. Copyright JetBrains s.r.o., (c) 2000-2018</string> 'idea.platform.prefix': 'AndroidStudio',
<key>CFBundleShortVersionString</key> },
<string>3.3</string> },
<key>CFBundleVersion</key> };
<string>AI-182.5107.16.33.5199772</string>
<key>JVMOptions</key>
<dict>
<key>Properties</key>
<dict>
<key>idea.platform.prefix</key>
<string>AndroidStudio</string>
<key>idea.paths.selector</key>
<string>AndroidStudio3.3</string>
</dict>
</dict>
</dict>
</plist>
''';
const String macStudioInfoPlistDefaultsResult =
'''
{
CFBundleGetInfoString = "Android Studio 3.3, build AI-182.5107.16.33.5199772. Copyright JetBrains s.r.o., (c) 2000-2018";
CFBundleShortVersionString = "3.3";
CFBundleVersion = "AI-182.5107.16.33.5199772";
JVMOptions = {
Properties = {
"idea.paths.selector" = "AndroidStudio3.3";
"idea.platform.prefix" = AndroidStudio;
};
};
}
''';
class MockIOSWorkflow extends Mock implements IOSWorkflow {} class MockPlistUtils extends Mock implements PlistParser {}
Platform linuxPlatform() { Platform linuxPlatform() {
return FakePlatform.fromPlatform(const LocalPlatform()) return FakePlatform.fromPlatform(const LocalPlatform())
...@@ -71,11 +43,11 @@ Platform macPlatform() { ...@@ -71,11 +43,11 @@ Platform macPlatform() {
void main() { void main() {
MemoryFileSystem fs; MemoryFileSystem fs;
MockIOSWorkflow iosWorkflow; MockPlistUtils plistUtils;
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
iosWorkflow = MockIOSWorkflow(); plistUtils = MockPlistUtils();
}); });
group('pluginsPath on Linux', () { group('pluginsPath on Linux', () {
...@@ -106,8 +78,7 @@ void main() { ...@@ -106,8 +78,7 @@ void main() {
fs.directory(studioInApplicationPlistFolder).createSync(recursive: true); fs.directory(studioInApplicationPlistFolder).createSync(recursive: true);
final String plistFilePath = fs.path.join(studioInApplicationPlistFolder, 'Info.plist'); final String plistFilePath = fs.path.join(studioInApplicationPlistFolder, 'Info.plist');
fs.file(plistFilePath).writeAsStringSync(macStudioInfoPlistValue); when(plistUtils.parseFile(plistFilePath)).thenReturn(macStudioInfoPlist);
when(iosWorkflow.getPlistValueFromFile(plistFilePath, null)).thenReturn(macStudioInfoPlistDefaultsResult);
final AndroidStudio studio = AndroidStudio.fromMacOSBundle(fs.directory(studioInApplicationPlistFolder)?.parent?.path); final AndroidStudio studio = AndroidStudio.fromMacOSBundle(fs.directory(studioInApplicationPlistFolder)?.parent?.path);
expect(studio, isNotNull); expect(studio, isNotNull);
expect(studio.pluginsPath, expect(studio.pluginsPath,
...@@ -117,47 +88,25 @@ void main() { ...@@ -117,47 +88,25 @@ void main() {
// Custom home paths are not supported on macOS nor Windows yet, // Custom home paths are not supported on macOS nor Windows yet,
// so we force the platform to fake Linux here. // so we force the platform to fake Linux here.
Platform: () => macPlatform(), Platform: () => macPlatform(),
IOSWorkflow: () => iosWorkflow, PlistParser: () => plistUtils,
}); });
testUsingContext('extracts custom paths for Android Studio downloaded by JetBrainsToolbox on Mac', () { testUsingContext('extracts custom paths for Android Studio downloaded by JetBrainsToolbox on Mac', () {
final String jetbrainsStudioInApplicationPlistFolder = fs.path.join(homeMac, 'Application', 'JetBrains Toolbox', 'Android Studio.app', 'Contents'); final String jetbrainsStudioInApplicationPlistFolder = fs.path.join(homeMac, 'Application', 'JetBrains Toolbox', 'Android Studio.app', 'Contents');
fs.directory(jetbrainsStudioInApplicationPlistFolder).createSync(recursive: true); fs.directory(jetbrainsStudioInApplicationPlistFolder).createSync(recursive: true);
const String jetbrainsInfoPlistValue = const Map<String, dynamic> jetbrainsInfoPlist = <String, dynamic>{
''' 'CFBundleLongVersionString': '3.3',
<?xml version='1.0' encoding='UTF-8'?> 'CFBundleShortVersionString': '3.3',
<!DOCTYPE plist PUBLIC '-//Apple Computer//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'> 'CFBundleVersion': '3.3',
<plist version="1.0"> 'JetBrainsToolboxApp': '$homeMac/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/183.5256920/Android Studio 3.3.app',
<dict> };
<key>CFBundleVersion</key>
<string>3.3</string>
<key>CFBundleLongVersionString</key>
<string>3.3</string>
<key>CFBundleShortVersionString</key>
<string>3.3</string>
<key>JetBrainsToolboxApp</key>
<string>$homeMac/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/183.5256920/Android Studio 3.3</string>
</dict>
</plist>
''';
const String jetbrainsInfoPlistDefaultsResult =
'''
{
CFBundleLongVersionString = "3.3";
CFBundleShortVersionString = "3.3";
CFBundleVersion = "3.3";
JetBrainsToolboxApp = "$homeMac/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/183.5256920/Android Studio 3.3.app";
}
''';
final String jetbrainsPlistFilePath = fs.path.join(jetbrainsStudioInApplicationPlistFolder, 'Info.plist'); final String jetbrainsPlistFilePath = fs.path.join(jetbrainsStudioInApplicationPlistFolder, 'Info.plist');
fs.file(jetbrainsPlistFilePath).writeAsStringSync(jetbrainsInfoPlistValue); when(plistUtils.parseFile(jetbrainsPlistFilePath)).thenReturn(jetbrainsInfoPlist);
when(iosWorkflow.getPlistValueFromFile(jetbrainsPlistFilePath, null)).thenReturn(jetbrainsInfoPlistDefaultsResult);
final String studioInApplicationPlistFolder = fs.path.join(fs.path.join(homeMac, 'Library', 'Application Support'), 'JetBrains', 'Toolbox', 'apps', 'AndroidStudio', 'ch-0', '183.5256920', fs.path.join('Android Studio 3.3.app', 'Contents')); final String studioInApplicationPlistFolder = fs.path.join(fs.path.join(homeMac, 'Library', 'Application Support'), 'JetBrains', 'Toolbox', 'apps', 'AndroidStudio', 'ch-0', '183.5256920', fs.path.join('Android Studio 3.3.app', 'Contents'));
fs.directory(studioInApplicationPlistFolder).createSync(recursive: true); fs.directory(studioInApplicationPlistFolder).createSync(recursive: true);
final String studioPlistFilePath = fs.path.join(studioInApplicationPlistFolder, 'Info.plist'); final String studioPlistFilePath = fs.path.join(studioInApplicationPlistFolder, 'Info.plist');
fs.file(studioPlistFilePath).writeAsStringSync(macStudioInfoPlistValue); when(plistUtils.parseFile(studioPlistFilePath)).thenReturn(macStudioInfoPlist);
when(iosWorkflow.getPlistValueFromFile(studioPlistFilePath, null)).thenReturn(macStudioInfoPlistDefaultsResult);
final AndroidStudio studio = AndroidStudio.fromMacOSBundle(fs.directory(jetbrainsStudioInApplicationPlistFolder)?.parent?.path); final AndroidStudio studio = AndroidStudio.fromMacOSBundle(fs.directory(jetbrainsStudioInApplicationPlistFolder)?.parent?.path);
expect(studio, isNotNull); expect(studio, isNotNull);
...@@ -168,7 +117,7 @@ void main() { ...@@ -168,7 +117,7 @@ void main() {
// Custom home paths are not supported on macOS nor Windows yet, // Custom home paths are not supported on macOS nor Windows yet,
// so we force the platform to fake Linux here. // so we force the platform to fake Linux here.
Platform: () => macPlatform(), Platform: () => macPlatform(),
IOSWorkflow: () => iosWorkflow, PlistParser: () => plistUtils,
}); });
}); });
......
...@@ -7,20 +7,19 @@ import 'dart:io' show ProcessResult; ...@@ -7,20 +7,19 @@ import 'dart:io' show ProcessResult;
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/fuchsia/application_package.dart'; import 'package:flutter_tools/src/fuchsia/application_package.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../src/common.dart'; import '../src/common.dart';
...@@ -190,7 +189,7 @@ void main() { ...@@ -190,7 +189,7 @@ void main() {
group('PrebuiltIOSApp', () { group('PrebuiltIOSApp', () {
final Map<Type, Generator> overrides = <Type, Generator>{ final Map<Type, Generator> overrides = <Type, Generator>{
FileSystem: () => MemoryFileSystem(), FileSystem: () => MemoryFileSystem(),
IOSWorkflow: () => MockIosWorkFlow(), PlistParser: () => MockPlistUtils(),
Platform: _kNoColorTerminalPlatform, Platform: _kNoColorTerminalPlatform,
OperatingSystemUtils: () => MockOperatingSystemUtils(), OperatingSystemUtils: () => MockOperatingSystemUtils(),
}; };
...@@ -587,9 +586,9 @@ const String _aaptDataWithDistNamespace = ...@@ -587,9 +586,9 @@ const String _aaptDataWithDistNamespace =
'''; ''';
class MockIosWorkFlow extends Mock implements IOSWorkflow { class MockPlistUtils extends Mock implements PlistParser {
@override @override
String getPlistValueFromFile(String path, String key) { String getValueFromFile(String path, String key) {
final File file = fs.file(path); final File file = fs.file(path);
if (!file.existsSync()) { if (!file.existsSync()) {
return null; return null;
......
...@@ -8,7 +8,7 @@ import 'package:flutter_tools/src/base/logger.dart'; ...@@ -8,7 +8,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/commands/build_aot.dart'; import 'package:flutter_tools/src/commands/build_aot.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -22,14 +22,14 @@ void main() { ...@@ -22,14 +22,14 @@ void main() {
MemoryFileSystem memoryFileSystem; MemoryFileSystem memoryFileSystem;
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
BufferLogger bufferLogger; BufferLogger bufferLogger;
MockIOSWorkflow mockIOSWorkflow; MockPlistUtils mockPlistUtils;
setUp(() { setUp(() {
mockXcode = MockXcode(); mockXcode = MockXcode();
memoryFileSystem = MemoryFileSystem(style: FileSystemStyle.posix); memoryFileSystem = MemoryFileSystem(style: FileSystemStyle.posix);
mockProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager();
bufferLogger = BufferLogger(); bufferLogger = BufferLogger();
mockIOSWorkflow = MockIOSWorkflow(); mockPlistUtils = MockPlistUtils();
}); });
testUsingContext('build aot validates building with bitcode requires a local engine', () async { testUsingContext('build aot validates building with bitcode requires a local engine', () async {
...@@ -87,7 +87,7 @@ void main() { ...@@ -87,7 +87,7 @@ void main() {
); );
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult)); when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult));
when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); when(mockPlistUtils.getValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)');
await expectToolExitLater( await expectToolExitLater(
validateBitcode(), validateBitcode(),
...@@ -102,7 +102,7 @@ void main() { ...@@ -102,7 +102,7 @@ void main() {
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Xcode: () => mockXcode, Xcode: () => mockXcode,
Logger: () => bufferLogger, Logger: () => bufferLogger,
IOSWorkflow: () => mockIOSWorkflow, PlistParser: () => mockPlistUtils,
}); });
testUsingContext('build aot validates and succeeds - same version of Xcode', () async { testUsingContext('build aot validates and succeeds - same version of Xcode', () async {
...@@ -121,7 +121,7 @@ void main() { ...@@ -121,7 +121,7 @@ void main() {
); );
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult)); when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult));
when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); when(mockPlistUtils.getValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)');
await validateBitcode(); await validateBitcode();
...@@ -132,7 +132,7 @@ void main() { ...@@ -132,7 +132,7 @@ void main() {
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Xcode: () => mockXcode, Xcode: () => mockXcode,
Logger: () => bufferLogger, Logger: () => bufferLogger,
IOSWorkflow: () => mockIOSWorkflow, PlistParser: () => mockPlistUtils,
}); });
testUsingContext('build aot validates and succeeds when user has newer version of Xcode', () async { testUsingContext('build aot validates and succeeds when user has newer version of Xcode', () async {
...@@ -151,7 +151,7 @@ void main() { ...@@ -151,7 +151,7 @@ void main() {
); );
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult)); when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult)); when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult));
when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)'); when(mockPlistUtils.getValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM version 10.0.1 (clang-1234.1.12.1)');
await validateBitcode(); await validateBitcode();
...@@ -162,9 +162,9 @@ void main() { ...@@ -162,9 +162,9 @@ void main() {
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
Xcode: () => mockXcode, Xcode: () => mockXcode,
Logger: () => bufferLogger, Logger: () => bufferLogger,
IOSWorkflow: () => mockIOSWorkflow, PlistParser: () => mockPlistUtils,
}); });
} }
class MockXcode extends Mock implements Xcode {} class MockXcode extends Mock implements Xcode {}
class MockIOSWorkflow extends Mock implements IOSWorkflow {} class MockPlistUtils extends Mock implements PlistParser {}
...@@ -316,7 +316,7 @@ class MockAndroidWorkflow extends AndroidWorkflow { ...@@ -316,7 +316,7 @@ class MockAndroidWorkflow extends AndroidWorkflow {
} }
class MockIOSWorkflow extends IOSWorkflow { class MockIOSWorkflow extends IOSWorkflow {
MockIOSWorkflow({ this.canListDevices =true }); MockIOSWorkflow({ this.canListDevices = true });
@override @override
final bool canListDevices; final bool canListDevices;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
const String base64PlistXml =
'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0I'
'FBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS'
'5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo'
'8ZGljdD4KICA8a2V5PkNGQnVuZGxlRXhlY3V0YWJsZTwva2V5PgogIDxzdHJpbmc+QXBwPC9z'
'dHJpbmc+CiAgPGtleT5DRkJ1bmRsZUlkZW50aWZpZXI8L2tleT4KICA8c3RyaW5nPmlvLmZsd'
'XR0ZXIuZmx1dHRlci5hcHA8L3N0cmluZz4KPC9kaWN0Pgo8L3BsaXN0Pgo=';
const String base64PlistBinary =
'YnBsaXN0MDDSAQIDBF8QEkNGQnVuZGxlRXhlY3V0YWJsZV8QEkNGQnVuZGxlSWRlbnRpZmllc'
'lNBcHBfEBZpby5mbHV0dGVyLmZsdXR0ZXIuYXBwCA0iNzsAAAAAAAABAQAAAAAAAAAFAAAAAA'
'AAAAAAAAAAAAAAVA==';
const String base64PlistJson =
'eyJDRkJ1bmRsZUV4ZWN1dGFibGUiOiJBcHAiLCJDRkJ1bmRsZUlkZW50aWZpZXIiOiJpby5mb'
'HV0dGVyLmZsdXR0ZXIuYXBwIn0=';
void main() {
group('PlistUtils', () {
// The tests herein explicitly don't use `MemoryFileSystem` or a mocked
// `ProcessManager` because doing so wouldn't actually test what we want to
// test, which is that the underlying tool we're using to parse Plist files
// works with the way we're calling it.
final Map<Type, Generator> overrides = <Type, Generator>{
FileSystem: () => const LocalFileSystemBlockingSetCurrentDirectory(),
ProcessManager: () => const LocalProcessManager(),
};
const PlistParser parser = PlistParser();
if (Platform.isMacOS) {
group('getValueFromFile', () {
File file;
setUp(() {
file = fs.file('foo.plist')..createSync();
});
tearDown(() {
file.deleteSync();
});
testUsingContext('works with xml file', () async {
file.writeAsBytesSync(base64.decode(base64PlistXml));
expect(parser.getValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(parser.getValueFromFile(file.absolute.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
testUsingContext('works with binary file', () async {
file.writeAsBytesSync(base64.decode(base64PlistBinary));
expect(parser.getValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(parser.getValueFromFile(file.absolute.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
testUsingContext('works with json file', () async {
file.writeAsBytesSync(base64.decode(base64PlistJson));
expect(parser.getValueFromFile(file.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(parser.getValueFromFile(file.absolute.path, 'CFBundleIdentifier'), 'io.flutter.flutter.app');
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
testUsingContext('returns null for non-existent plist file', () async {
expect(parser.getValueFromFile('missing.plist', 'CFBundleIdentifier'), null);
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
testUsingContext('returns null for non-existent key within plist', () async {
file.writeAsBytesSync(base64.decode(base64PlistXml));
expect(parser.getValueFromFile(file.path, 'BadKey'), null);
expect(parser.getValueFromFile(file.absolute.path, 'BadKey'), null);
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
testUsingContext('returns null for malformed plist file', () async {
file.writeAsBytesSync(const <int>[1, 2, 3, 4, 5, 6]);
expect(parser.getValueFromFile(file.path, 'CFBundleIdentifier'), null);
expect(testLogger.statusText, isNotEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
});
} else {
testUsingContext('throws when /usr/bin/plutil is not found', () async {
expect(
() => parser.getValueFromFile('irrelevant.plist', 'ununsed'),
throwsA(isA<FileNotFoundException>()),
);
expect(testLogger.statusText, isEmpty);
expect(testLogger.errorText, isEmpty);
}, overrides: overrides);
}
});
}
...@@ -11,8 +11,8 @@ import 'package:file/memory.dart'; ...@@ -11,8 +11,8 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
...@@ -30,7 +30,7 @@ class MockProcess extends Mock implements Process {} ...@@ -30,7 +30,7 @@ class MockProcess extends Mock implements Process {}
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockXcode extends Mock implements Xcode {} class MockXcode extends Mock implements Xcode {}
class MockSimControl extends Mock implements SimControl {} class MockSimControl extends Mock implements SimControl {}
class MockIOSWorkflow extends Mock implements IOSWorkflow {} class MockPlistUtils extends Mock implements PlistParser {}
void main() { void main() {
FakePlatform osx; FakePlatform osx;
...@@ -455,7 +455,7 @@ void main() { ...@@ -455,7 +455,7 @@ void main() {
testUsingContext("startApp uses compiled app's Info.plist to find CFBundleIdentifier", () async { testUsingContext("startApp uses compiled app's Info.plist to find CFBundleIdentifier", () async {
final IOSSimulator device = IOSSimulator('x', name: 'iPhone SE', simulatorCategory: 'iOS 11.2'); final IOSSimulator device = IOSSimulator('x', name: 'iPhone SE', simulatorCategory: 'iOS 11.2');
when(iosWorkflow.getPlistValueFromFile(any, any)).thenReturn('correct'); when(PlistParser.instance.getValueFromFile(any, any)).thenReturn('correct');
final Directory mockDir = fs.currentDirectory; final Directory mockDir = fs.currentDirectory;
final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir); final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir);
...@@ -468,7 +468,7 @@ void main() { ...@@ -468,7 +468,7 @@ void main() {
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
SimControl: () => simControl, SimControl: () => simControl,
IOSWorkflow: () => MockIOSWorkflow() PlistParser: () => MockPlistUtils(),
}, },
); );
}); });
......
...@@ -12,7 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -12,7 +12,7 @@ import 'package:flutter_tools/src/base/file_system.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/plist_parser.dart';
import 'package:flutter_tools/src/ios/xcodeproj.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:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -278,18 +278,18 @@ apply plugin: 'kotlin-android' ...@@ -278,18 +278,18 @@ apply plugin: 'kotlin-android'
group('product bundle identifier', () { group('product bundle identifier', () {
MemoryFileSystem fs; MemoryFileSystem fs;
MockIOSWorkflow mockIOSWorkflow; MockPlistUtils mockPlistUtils;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter; MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
mockIOSWorkflow = MockIOSWorkflow(); mockPlistUtils = MockPlistUtils();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
}); });
void testWithMocks(String description, Future<void> testMethod()) { void testWithMocks(String description, Future<void> testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{ testUsingContext(description, testMethod, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
IOSWorkflow: () => mockIOSWorkflow, PlistParser: () => mockPlistUtils,
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
} }
...@@ -307,7 +307,7 @@ apply plugin: 'kotlin-android' ...@@ -307,7 +307,7 @@ apply plugin: 'kotlin-android'
}); });
testWithMocks('from plist, if no variables', () async { testWithMocks('from plist, if no variables', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('io.flutter.someProject'); when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('io.flutter.someProject');
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); expect(project.ios.productBundleIdentifier, 'io.flutter.someProject');
}); });
testWithMocks('from pbxproj and plist, if default variable', () async { testWithMocks('from pbxproj and plist, if default variable', () async {
...@@ -315,7 +315,7 @@ apply plugin: 'kotlin-android' ...@@ -315,7 +315,7 @@ apply plugin: 'kotlin-android'
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
return projectFileWithBundleId('io.flutter.someProject'); return projectFileWithBundleId('io.flutter.someProject');
}); });
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER)'); when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER)');
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); expect(project.ios.productBundleIdentifier, 'io.flutter.someProject');
}); });
testWithMocks('from pbxproj and plist, by substitution', () async { testWithMocks('from pbxproj and plist, by substitution', () async {
...@@ -324,7 +324,7 @@ apply plugin: 'kotlin-android' ...@@ -324,7 +324,7 @@ apply plugin: 'kotlin-android'
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
'SUFFIX': 'suffix', 'SUFFIX': 'suffix',
}); });
when(mockIOSWorkflow.getPlistValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER).\$(SUFFIX)'); when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER).\$(SUFFIX)');
expect(project.ios.productBundleIdentifier, 'io.flutter.someProject.suffix'); expect(project.ios.productBundleIdentifier, 'io.flutter.someProject.suffix');
}); });
testWithMocks('empty surrounded by quotes', () async { testWithMocks('empty surrounded by quotes', () async {
...@@ -636,7 +636,7 @@ File androidPluginRegistrant(Directory parent) { ...@@ -636,7 +636,7 @@ File androidPluginRegistrant(Directory parent) {
.childFile('GeneratedPluginRegistrant.java'); .childFile('GeneratedPluginRegistrant.java');
} }
class MockIOSWorkflow extends Mock implements IOSWorkflow {} class MockPlistUtils extends Mock implements PlistParser {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter { class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {
@override @override
......
...@@ -18,6 +18,7 @@ import 'package:flutter_tools/src/cache.dart'; ...@@ -18,6 +18,7 @@ import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/context_runner.dart'; import 'package:flutter_tools/src/context_runner.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
...@@ -86,8 +87,9 @@ void testUsingContext( ...@@ -86,8 +87,9 @@ void testUsingContext(
SimControl: () => MockSimControl(), SimControl: () => MockSimControl(),
Usage: () => FakeUsage(), Usage: () => FakeUsage(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
FileSystem: () => LocalFileSystemBlockingSetCurrentDirectory(), FileSystem: () => const LocalFileSystemBlockingSetCurrentDirectory(),
TimeoutConfiguration: () => const TimeoutConfiguration(), TimeoutConfiguration: () => const TimeoutConfiguration(),
PlistParser: () => FakePlistParser(),
}, },
body: () { body: () {
final String flutterRoot = getFlutterRoot(); final String flutterRoot = getFlutterRoot();
...@@ -356,7 +358,17 @@ class MockClock extends Mock implements SystemClock {} ...@@ -356,7 +358,17 @@ class MockClock extends Mock implements SystemClock {}
class MockHttpClient extends Mock implements HttpClient {} class MockHttpClient extends Mock implements HttpClient {}
class FakePlistParser implements PlistParser {
@override
Map<String, dynamic> parseFile(String plistFilePath) => const <String, dynamic>{};
@override
String getValueFromFile(String plistFilePath, String key) => null;
}
class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem { class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem {
const LocalFileSystemBlockingSetCurrentDirectory();
@override @override
set currentDirectory(dynamic value) { set currentDirectory(dynamic value) {
throw 'fs.currentDirectory should not be set on the local file system during ' throw 'fs.currentDirectory should not be set on the local file system during '
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment