Unverified Commit 80619f10 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove globals from plist parser and update tests (#51444)

parent d4226566
...@@ -41,14 +41,14 @@ class AndroidStudio implements Comparable<AndroidStudio> { ...@@ -41,14 +41,14 @@ class AndroidStudio implements Comparable<AndroidStudio> {
factory AndroidStudio.fromMacOSBundle(String bundlePath) { factory AndroidStudio.fromMacOSBundle(String bundlePath) {
String studioPath = globals.fs.path.join(bundlePath, 'Contents'); String studioPath = globals.fs.path.join(bundlePath, 'Contents');
String plistFile = globals.fs.path.join(studioPath, 'Info.plist'); 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. // 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 = plistValues['JetBrainsToolboxApp'] as String; final String jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'] as String;
if (jetBrainsToolboxAppBundlePath != null) { if (jetBrainsToolboxAppBundlePath != null) {
studioPath = globals.fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents'); studioPath = globals.fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents');
plistFile = globals.fs.path.join(studioPath, 'Info.plist'); 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; final String versionString = plistValues[PlistParser.kCFBundleShortVersionStringKey] as String;
......
...@@ -312,7 +312,7 @@ abstract class IOSApp extends ApplicationPackage { ...@@ -312,7 +312,7 @@ abstract class IOSApp extends ApplicationPackage {
globals.printError('Invalid prebuilt iOS app. Does not contain Info.plist.'); globals.printError('Invalid prebuilt iOS app. Does not contain Info.plist.');
return null; return null;
} }
final String id = PlistParser.instance.getValueFromFile( final String id = globals.plistParser.getValueFromFile(
plistPath, plistPath,
PlistParser.kCFBundleIdentifierKey, PlistParser.kCFBundleIdentifierKey,
); );
......
...@@ -862,7 +862,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -862,7 +862,7 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
@override @override
String get version { String get version {
_version ??= PlistParser.instance.getValueFromFile( _version ??= globals.plistParser.getValueFromFile(
plistFile, plistFile,
PlistParser.kCFBundleShortVersionStringKey, PlistParser.kCFBundleShortVersionStringKey,
) ?? 'unknown'; ) ?? 'unknown';
...@@ -876,7 +876,8 @@ class IntelliJValidatorOnMac extends IntelliJValidator { ...@@ -876,7 +876,8 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
return _pluginsPath; return _pluginsPath;
} }
final String altLocation = PlistParser.instance.getValueFromFile(plistFile, 'JetBrainsToolboxApp'); final String altLocation = globals.plistParser
.getValueFromFile(plistFile, 'JetBrainsToolboxApp');
if (altLocation != null) { if (altLocation != null) {
_pluginsPath = altLocation + '.plugins'; _pluginsPath = altLocation + '.plugins';
......
...@@ -22,6 +22,7 @@ import 'base/user_messages.dart'; ...@@ -22,6 +22,7 @@ import 'base/user_messages.dart';
import 'cache.dart'; import 'cache.dart';
import 'ios/ios_deploy.dart'; import 'ios/ios_deploy.dart';
import 'ios/mac.dart'; import 'ios/mac.dart';
import 'ios/plist_parser.dart';
import 'macos/xcode.dart'; import 'macos/xcode.dart';
import 'persistent_tool_state.dart'; import 'persistent_tool_state.dart';
import 'version.dart'; import 'version.dart';
...@@ -150,5 +151,12 @@ final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal( ...@@ -150,5 +151,12 @@ final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal(
/// The global Stdio wrapper. /// The global Stdio wrapper.
Stdio get stdio => context.get<Stdio>() ?? const Stdio(); 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. /// The [ChromeLauncher] instance.
ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>(); ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>();
...@@ -9,7 +9,6 @@ import '../base/process.dart'; ...@@ -9,7 +9,6 @@ import '../base/process.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/plist_parser.dart';
import '../macos/xcode.dart'; import '../macos/xcode.dart';
const bool kBitcodeEnabledDefault = false; const bool kBitcodeEnabledDefault = false;
...@@ -28,7 +27,7 @@ Future<void> validateBitcode(BuildMode buildMode, TargetPlatform targetPlatform) ...@@ -28,7 +27,7 @@ Future<void> validateBitcode(BuildMode buildMode, TargetPlatform targetPlatform)
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 = PlistParser.instance.getValueFromFile( final String engineClangVersion = globals.plistParser.getValueFromFile(
globals.fs.path.join(flutterFrameworkPath, 'Info.plist'), globals.fs.path.join(flutterFrameworkPath, 'Info.plist'),
'ClangVersion', 'ClangVersion',
); );
......
...@@ -2,23 +2,33 @@ ...@@ -2,23 +2,33 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals;
class PlistParser { 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 kCFBundleIdentifierKey = 'CFBundleIdentifier';
static const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString'; static const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString';
static const String kCFBundleExecutable = 'CFBundleExecutable'; static const String kCFBundleExecutable = 'CFBundleExecutable';
static PlistParser get instance => context.get<PlistParser>() ?? const PlistParser();
/// Parses the plist file located at [plistFilePath] and returns the /// Parses the plist file located at [plistFilePath] and returns the
/// associated map of key/value property list pairs. /// associated map of key/value property list pairs.
/// ///
...@@ -29,26 +39,26 @@ class PlistParser { ...@@ -29,26 +39,26 @@ class PlistParser {
Map<String, dynamic> parseFile(String plistFilePath) { Map<String, dynamic> parseFile(String plistFilePath) {
assert(plistFilePath != null); assert(plistFilePath != null);
const String executable = '/usr/bin/plutil'; const String executable = '/usr/bin/plutil';
if (!globals.fs.isFileSync(executable)) { if (!_fileSystem.isFileSync(executable)) {
throw const FileNotFoundException(executable); throw const FileNotFoundException(executable);
} }
if (!globals.fs.isFileSync(plistFilePath)) { if (!_fileSystem.isFileSync(plistFilePath)) {
return const <String, dynamic>{}; return const <String, dynamic>{};
} }
final String normalizedPlistPath = globals.fs.path.absolute(plistFilePath); final String normalizedPlistPath = _fileSystem.path.absolute(plistFilePath);
try { try {
final List<String> args = <String>[ final List<String> args = <String>[
executable, '-convert', 'json', '-o', '-', normalizedPlistPath, executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
]; ];
final String jsonContent = processUtils.runSync( final String jsonContent = _processUtils.runSync(
args, args,
throwOnError: true, throwOnError: true,
).stdout.trim(); ).stdout.trim();
return castStringKeyedMap(json.decode(jsonContent)); return castStringKeyedMap(json.decode(jsonContent));
} on ProcessException catch (error) { } on ProcessException catch (error) {
globals.printTrace('$error'); _logger.printTrace('$error');
return const <String, dynamic>{}; return const <String, dynamic>{};
} }
} }
......
...@@ -392,7 +392,7 @@ class IOSSimulator extends Device { ...@@ -392,7 +392,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 = globals.fs.path.join(package.simulatorBundlePath, 'Info.plist'); 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); await SimControl.instance.launch(id, bundleIdentifier, args);
} catch (error) { } catch (error) {
......
...@@ -66,7 +66,7 @@ abstract class MacOSApp extends ApplicationPackage { ...@@ -66,7 +66,7 @@ abstract class MacOSApp extends ApplicationPackage {
globals.printError('Invalid prebuilt macOS app. Does not contain Info.plist.'); globals.printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
return null; 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 id = propertyValues[PlistParser.kCFBundleIdentifierKey] as String;
final String executableName = propertyValues[PlistParser.kCFBundleExecutable] as String; final String executableName = propertyValues[PlistParser.kCFBundleExecutable] as String;
if (id == null) { if (id == null) {
......
...@@ -402,7 +402,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -402,7 +402,7 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
// Try parsing the default, first. // Try parsing the default, first.
if (defaultInfoPlist.existsSync()) { if (defaultInfoPlist.existsSync()) {
try { try {
fromPlist = PlistParser.instance.getValueFromFile( fromPlist = globals.plistParser.getValueFromFile(
defaultHostInfoPlist.path, defaultHostInfoPlist.path,
PlistParser.kCFBundleIdentifierKey, PlistParser.kCFBundleIdentifierKey,
); );
......
...@@ -13,7 +13,6 @@ import 'package:flutter_tools/src/base/user_messages.dart'; ...@@ -13,7 +13,6 @@ import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals; 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/proxy_validator.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/vscode/vscode.dart'; import 'package:flutter_tools/src/vscode/vscode.dart';
...@@ -77,8 +76,6 @@ void main() { ...@@ -77,8 +76,6 @@ void main() {
final IntelliJValidatorOnMac validatorNotViaToolbox = IntelliJValidatorOnMac('Test', 'Test', pathNotViaToolbox); final IntelliJValidatorOnMac validatorNotViaToolbox = IntelliJValidatorOnMac('Test', 'Test', pathNotViaToolbox);
expect(validatorNotViaToolbox.plistFile, 'test/data/intellij/mac_not_via_toolbox/Contents/Info.plist'); 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 { testUsingContext('vs code validator when both installed', () async {
......
...@@ -3,13 +3,14 @@ ...@@ -3,13 +3,14 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io' as io;
import 'package:file/file.dart'; import 'package:file/file.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/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/ios/plist_parser.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 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -33,84 +34,94 @@ const String base64PlistJson = ...@@ -33,84 +34,94 @@ const String base64PlistJson =
'HV0dGVyLmZsdXR0ZXIuYXBwIn0='; 'HV0dGVyLmZsdXR0ZXIuYXBwIn0=';
void main() { void main() {
group('PlistUtils', () { // The tests herein explicitly don't use `MemoryFileSystem` or a mocked
// The tests herein explicitly don't use `MemoryFileSystem` or a mocked // `ProcessManager` because doing so wouldn't actually test what we want to
// `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
// test, which is that the underlying tool we're using to parse Plist files // works with the way we're calling it.
// works with the way we're calling it. FileSystem fileSystem;
final Map<Type, Generator> overrides = <Type, Generator>{ ProcessManager processManager;
FileSystem: () => const LocalFileSystemBlockingSetCurrentDirectory(), File file;
ProcessManager: () => const LocalProcessManager(), PlistParser parser;
}; BufferLogger logger;
const PlistParser parser = PlistParser(); setUp(() {
logger = BufferLogger(
if (Platform.isMacOS) { outputPreferences: OutputPreferences.test(),
group('getValueFromFile', () { terminal: AnsiTerminal(
File file; platform: const LocalPlatform(),
stdio: null,
setUp(() { ),
file = globals.fs.file('foo.plist')..createSync(); );
}); fileSystem = const LocalFileSystemBlockingSetCurrentDirectory();
processManager = const LocalProcessManager();
tearDown(() { parser = PlistParser(
file.deleteSync(); fileSystem: fileSystem,
}); processManager: processManager,
logger: logger,
testUsingContext('works with xml file', () async { );
file.writeAsBytesSync(base64.decode(base64PlistXml)); file = fileSystem.file('foo.plist')..createSync();
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); tearDown(() {
expect(testLogger.errorText, isEmpty); file.deleteSync();
}, 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);
}
}); });
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);
} }
...@@ -498,7 +498,7 @@ void main() { ...@@ -498,7 +498,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(PlistParser.instance.getValueFromFile(any, any)).thenReturn('correct'); when(globals.plistParser.getValueFromFile(any, any)).thenReturn('correct');
final Directory mockDir = globals.fs.currentDirectory; final Directory mockDir = globals.fs.currentDirectory;
final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir); final IOSApp package = PrebuiltIOSApp(projectBundleId: 'incorrect', bundleName: 'name', bundleDir: mockDir);
......
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