[flutter_tools] remove globals from plist parser and update tests (#51444)
diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart
index b33c308..961b673 100644
--- a/packages/flutter_tools/lib/src/android/android_studio.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio.dart
@@ -41,14 +41,14 @@
factory AndroidStudio.fromMacOSBundle(String bundlePath) {
String studioPath = globals.fs.path.join(bundlePath, 'Contents');
String plistFile = globals.fs.path.join(studioPath, 'Info.plist');
- Map<String, dynamic> plistValues = PlistParser.instance.parseFile(plistFile);
+ Map<String, dynamic> plistValues = globals.plistParser.parseFile(plistFile);
// 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.
final String jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'] as String;
if (jetBrainsToolboxAppBundlePath != null) {
studioPath = globals.fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents');
plistFile = globals.fs.path.join(studioPath, 'Info.plist');
- plistValues = PlistParser.instance.parseFile(plistFile);
+ plistValues = globals.plistParser.parseFile(plistFile);
}
final String versionString = plistValues[PlistParser.kCFBundleShortVersionStringKey] as String;
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 0c0448b..251e856 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -312,7 +312,7 @@
globals.printError('Invalid prebuilt iOS app. Does not contain Info.plist.');
return null;
}
- final String id = PlistParser.instance.getValueFromFile(
+ final String id = globals.plistParser.getValueFromFile(
plistPath,
PlistParser.kCFBundleIdentifierKey,
);
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index f5a8a2c..74fd16e 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -862,7 +862,7 @@
@override
String get version {
- _version ??= PlistParser.instance.getValueFromFile(
+ _version ??= globals.plistParser.getValueFromFile(
plistFile,
PlistParser.kCFBundleShortVersionStringKey,
) ?? 'unknown';
@@ -876,7 +876,8 @@
return _pluginsPath;
}
- final String altLocation = PlistParser.instance.getValueFromFile(plistFile, 'JetBrainsToolboxApp');
+ final String altLocation = globals.plistParser
+ .getValueFromFile(plistFile, 'JetBrainsToolboxApp');
if (altLocation != null) {
_pluginsPath = altLocation + '.plugins';
diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart
index 4342c73..5ccdc82 100644
--- a/packages/flutter_tools/lib/src/globals.dart
+++ b/packages/flutter_tools/lib/src/globals.dart
@@ -22,6 +22,7 @@
import 'cache.dart';
import 'ios/ios_deploy.dart';
import 'ios/mac.dart';
+import 'ios/plist_parser.dart';
import 'macos/xcode.dart';
import 'persistent_tool_state.dart';
import 'version.dart';
@@ -150,5 +151,12 @@
/// The global Stdio wrapper.
Stdio get stdio => context.get<Stdio>() ?? const Stdio();
+PlistParser get plistParser => context.get<PlistParser>() ?? (_defaultInstance ??= PlistParser(
+ fileSystem: fs,
+ processManager: processManager,
+ logger: logger,
+));
+PlistParser _defaultInstance;
+
/// The [ChromeLauncher] instance.
ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>();
diff --git a/packages/flutter_tools/lib/src/ios/bitcode.dart b/packages/flutter_tools/lib/src/ios/bitcode.dart
index ed08cb7..ec00545 100644
--- a/packages/flutter_tools/lib/src/ios/bitcode.dart
+++ b/packages/flutter_tools/lib/src/ios/bitcode.dart
@@ -9,7 +9,6 @@
import '../base/version.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
-import '../ios/plist_parser.dart';
import '../macos/xcode.dart';
const bool kBitcodeEnabledDefault = false;
@@ -28,7 +27,7 @@
final RunResult clangResult = await xcode.clang(<String>['--version']);
final String clangVersion = clangResult.stdout.split('\n').first;
- final String engineClangVersion = PlistParser.instance.getValueFromFile(
+ final String engineClangVersion = globals.plistParser.getValueFromFile(
globals.fs.path.join(flutterFrameworkPath, 'Info.plist'),
'ClangVersion',
);
diff --git a/packages/flutter_tools/lib/src/ios/plist_parser.dart b/packages/flutter_tools/lib/src/ios/plist_parser.dart
index 8a2bcf3..183637d 100644
--- a/packages/flutter_tools/lib/src/ios/plist_parser.dart
+++ b/packages/flutter_tools/lib/src/ios/plist_parser.dart
@@ -2,23 +2,33 @@
// 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 'package:meta/meta.dart';
+import 'package:process/process.dart';
+
import '../base/file_system.dart';
import '../base/io.dart';
+import '../base/logger.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../convert.dart';
-import '../globals.dart' as globals;
class PlistParser {
- const PlistParser();
+ PlistParser({
+ @required FileSystem fileSystem,
+ @required Logger logger,
+ @required ProcessManager processManager,
+ }) : _fileSystem = fileSystem,
+ _logger = logger,
+ _processUtils = ProcessUtils(logger: logger, processManager: processManager);
+
+ final FileSystem _fileSystem;
+ final Logger _logger;
+ final ProcessUtils _processUtils;
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.
///
@@ -29,26 +39,26 @@
Map<String, dynamic> parseFile(String plistFilePath) {
assert(plistFilePath != null);
const String executable = '/usr/bin/plutil';
- if (!globals.fs.isFileSync(executable)) {
+ if (!_fileSystem.isFileSync(executable)) {
throw const FileNotFoundException(executable);
}
- if (!globals.fs.isFileSync(plistFilePath)) {
+ if (!_fileSystem.isFileSync(plistFilePath)) {
return const <String, dynamic>{};
}
- final String normalizedPlistPath = globals.fs.path.absolute(plistFilePath);
+ final String normalizedPlistPath = _fileSystem.path.absolute(plistFilePath);
try {
final List<String> args = <String>[
executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
];
- final String jsonContent = processUtils.runSync(
+ final String jsonContent = _processUtils.runSync(
args,
throwOnError: true,
).stdout.trim();
return castStringKeyedMap(json.decode(jsonContent));
} on ProcessException catch (error) {
- globals.printTrace('$error');
+ _logger.printTrace('$error');
return const <String, dynamic>{};
}
}
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 0c45ff6..d9f8fe3 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -392,7 +392,7 @@
// parsing the xcodeproj or configuration files.
// See https://github.com/flutter/flutter/issues/31037 for more information.
final String plistPath = globals.fs.path.join(package.simulatorBundlePath, 'Info.plist');
- final String bundleIdentifier = PlistParser.instance.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
+ final String bundleIdentifier = globals.plistParser.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
await SimControl.instance.launch(id, bundleIdentifier, args);
} catch (error) {
diff --git a/packages/flutter_tools/lib/src/macos/application_package.dart b/packages/flutter_tools/lib/src/macos/application_package.dart
index 5b820fd..212c8e0 100644
--- a/packages/flutter_tools/lib/src/macos/application_package.dart
+++ b/packages/flutter_tools/lib/src/macos/application_package.dart
@@ -66,7 +66,7 @@
globals.printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
return null;
}
- final Map<String, dynamic> propertyValues = PlistParser.instance.parseFile(plistPath);
+ final Map<String, dynamic> propertyValues = globals.plistParser.parseFile(plistPath);
final String id = propertyValues[PlistParser.kCFBundleIdentifierKey] as String;
final String executableName = propertyValues[PlistParser.kCFBundleExecutable] as String;
if (id == null) {
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 4ce1cd3..06ca2d6 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -402,7 +402,7 @@
// Try parsing the default, first.
if (defaultInfoPlist.existsSync()) {
try {
- fromPlist = PlistParser.instance.getValueFromFile(
+ fromPlist = globals.plistParser.getValueFromFile(
defaultHostInfoPlist.path,
PlistParser.kCFBundleIdentifierKey,
);
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart
index b8b253b..d4b8891 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart
@@ -13,7 +13,6 @@
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
-import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/proxy_validator.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/vscode/vscode.dart';
@@ -77,8 +76,6 @@
final IntelliJValidatorOnMac validatorNotViaToolbox = IntelliJValidatorOnMac('Test', 'Test', pathNotViaToolbox);
expect(validatorNotViaToolbox.plistFile, 'test/data/intellij/mac_not_via_toolbox/Contents/Info.plist');
- }, overrides: <Type, Generator>{
- PlistParser: () => const PlistParser(),
});
testUsingContext('vs code validator when both installed', () async {
diff --git a/packages/flutter_tools/test/general.shard/ios/plist_parser_test.dart b/packages/flutter_tools/test/general.shard/ios/plist_parser_test.dart
index 4f32aaf..8de5a0e 100644
--- a/packages/flutter_tools/test/general.shard/ios/plist_parser_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/plist_parser_test.dart
@@ -3,13 +3,14 @@
// found in the LICENSE file.
import 'dart:convert';
-import 'dart:io';
+import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart';
-import 'package:flutter_tools/src/globals.dart' as globals;
-
+import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
@@ -33,84 +34,94 @@
'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(),
- };
+ // 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.
+ FileSystem fileSystem;
+ ProcessManager processManager;
+ File file;
+ PlistParser parser;
+ BufferLogger logger;
- const PlistParser parser = PlistParser();
-
- if (Platform.isMacOS) {
- group('getValueFromFile', () {
- File file;
-
- setUp(() {
- file = globals.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);
- }
+ setUp(() {
+ logger = BufferLogger(
+ outputPreferences: OutputPreferences.test(),
+ terminal: AnsiTerminal(
+ platform: const LocalPlatform(),
+ stdio: null,
+ ),
+ );
+ fileSystem = const LocalFileSystemBlockingSetCurrentDirectory();
+ processManager = const LocalProcessManager();
+ parser = PlistParser(
+ fileSystem: fileSystem,
+ processManager: processManager,
+ logger: logger,
+ );
+ file = fileSystem.file('foo.plist')..createSync();
});
+
+ tearDown(() {
+ file.deleteSync();
+ });
+
+ testWithoutContext('PlistParser.getValueFromFile works with xml file', () {
+ 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(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile works with binary file', () {
+ 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(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile works with json file', () {
+ 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(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile returns null for non-existent plist file', () {
+ expect(parser.getValueFromFile('missing.plist', 'CFBundleIdentifier'), null);
+ expect(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile returns null for non-existent key within plist', () {
+ file.writeAsBytesSync(base64.decode(base64PlistXml));
+
+ expect(parser.getValueFromFile(file.path, 'BadKey'), null);
+ expect(parser.getValueFromFile(file.absolute.path, 'BadKey'), null);
+ expect(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile returns null for malformed plist file', () {
+ file.writeAsBytesSync(const <int>[1, 2, 3, 4, 5, 6]);
+
+ expect(parser.getValueFromFile(file.path, 'CFBundleIdentifier'), null);
+ expect(logger.statusText, isNotEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: !io.Platform.isMacOS);
+
+ testWithoutContext('PlistParser.getValueFromFile throws when /usr/bin/plutil is not found', () async {
+ expect(
+ () => parser.getValueFromFile('irrelevant.plist', 'ununsed'),
+ throwsA(isA<FileNotFoundException>()),
+ );
+ expect(logger.statusText, isEmpty);
+ expect(logger.errorText, isEmpty);
+ }, skip: io.Platform.isMacOS);
}
diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
index 097fec9..441058c 100644
--- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart
@@ -498,7 +498,7 @@
testUsingContext("startApp uses compiled app's Info.plist to find CFBundleIdentifier", () async {
final IOSSimulator device = IOSSimulator('x', name: 'iPhone SE', simulatorCategory: 'iOS 11.2');
- when(PlistParser.instance.getValueFromFile(any, any)).thenReturn('correct');
+ when(globals.plistParser.getValueFromFile(any, any)).thenReturn('correct');
final Directory mockDir = globals.fs.currentDirectory;
final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir);