Skip to content

Commit c22ce95

Browse files
authored
Change from using defaults to plutil for Plist parsing (flutter#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 flutter#37701
1 parent 50b5502 commit c22ce95

19 files changed

+296
-195
lines changed

packages/flutter_tools/lib/src/android/android_studio.dart

+13-18
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import '../base/platform.dart';
1010
import '../base/process_manager.dart';
1111
import '../base/version.dart';
1212
import '../globals.dart';
13-
import '../ios/ios_workflow.dart';
14-
import '../ios/plist_utils.dart' as plist;
13+
import '../ios/plist_parser.dart';
1514

1615
AndroidStudio get androidStudio => context.get<AndroidStudio>();
1716

@@ -43,34 +42,30 @@ class AndroidStudio implements Comparable<AndroidStudio> {
4342
factory AndroidStudio.fromMacOSBundle(String bundlePath) {
4443
String studioPath = fs.path.join(bundlePath, 'Contents');
4544
String plistFile = fs.path.join(studioPath, 'Info.plist');
46-
String plistValue = iosWorkflow.getPlistValueFromFile(
47-
plistFile,
48-
null,
49-
);
50-
final RegExp _pathsSelectorMatcher = RegExp(r'"idea.paths.selector" = "[^;]+"');
51-
final RegExp _jetBrainsToolboxAppMatcher = RegExp(r'JetBrainsToolboxApp = "[^;]+"');
45+
Map<String, dynamic> plistValues = PlistParser.instance.parseFile(plistFile);
5246
// As AndroidStudio managed by JetBrainsToolbox could have a wrapper pointing to the real Android Studio.
5347
// Check if we've found a JetBrainsToolbox wrapper and deal with it properly.
54-
final String jetBrainsToolboxAppBundlePath = extractStudioPlistValueWithMatcher(plistValue, _jetBrainsToolboxAppMatcher);
48+
final String jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'];
5549
if (jetBrainsToolboxAppBundlePath != null) {
5650
studioPath = fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents');
5751
plistFile = fs.path.join(studioPath, 'Info.plist');
58-
plistValue = iosWorkflow.getPlistValueFromFile(
59-
plistFile,
60-
null,
61-
);
52+
plistValues = PlistParser.instance.parseFile(plistFile);
6253
}
6354

64-
final String versionString = iosWorkflow.getPlistValueFromFile(
65-
plistFile,
66-
plist.kCFBundleShortVersionStringKey,
67-
);
55+
final String versionString = plistValues[PlistParser.kCFBundleShortVersionStringKey];
6856

6957
Version version;
7058
if (versionString != null)
7159
version = Version.parse(versionString);
7260

73-
final String pathsSelectorValue = extractStudioPlistValueWithMatcher(plistValue, _pathsSelectorMatcher);
61+
String pathsSelectorValue;
62+
final Map<String, dynamic> jvmOptions = plistValues['JVMOptions'];
63+
if (jvmOptions != null) {
64+
final Map<String, dynamic> jvmProperties = jvmOptions['Properties'];
65+
if (jvmProperties != null) {
66+
pathsSelectorValue = jvmProperties['idea.paths.selector'];
67+
}
68+
}
7469
final String presetPluginsPath = pathsSelectorValue == null
7570
? null
7671
: fs.path.join(homeDirPath, 'Library', 'Application Support', '$pathsSelectorValue');

packages/flutter_tools/lib/src/application_package.dart

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import 'base/user_messages.dart';
1919
import 'build_info.dart';
2020
import 'fuchsia/application_package.dart';
2121
import 'globals.dart';
22-
import 'ios/ios_workflow.dart';
23-
import 'ios/plist_utils.dart' as plist;
22+
import 'ios/plist_parser.dart';
2423
import 'linux/application_package.dart';
2524
import 'macos/application_package.dart';
2625
import 'project.dart';
@@ -309,9 +308,9 @@ abstract class IOSApp extends ApplicationPackage {
309308
printError('Invalid prebuilt iOS app. Does not contain Info.plist.');
310309
return null;
311310
}
312-
final String id = iosWorkflow.getPlistValueFromFile(
311+
final String id = PlistParser.instance.getValueFromFile(
313312
plistPath,
314-
plist.kCFBundleIdentifierKey,
313+
PlistParser.kCFBundleIdentifierKey,
315314
);
316315
if (id == null) {
317316
printError('Invalid prebuilt iOS app. Info.plist does not contain bundle identifier');

packages/flutter_tools/lib/src/base/file_system.dart

+10
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,13 @@ bool isOlderThanReference({ @required FileSystemEntity entity, @required File re
157157
return referenceFile.existsSync()
158158
&& referenceFile.lastModifiedSync().isAfter(entity.statSync().modified);
159159
}
160+
161+
/// Exception indicating that a file that was expected to exist was not found.
162+
class FileNotFoundException implements IOException {
163+
const FileNotFoundException(this.path);
164+
165+
final String path;
166+
167+
@override
168+
String toString() => 'File not found: $path';
169+
}

packages/flutter_tools/lib/src/commands/build_aot.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import '../base/version.dart';
1515
import '../build_info.dart';
1616
import '../dart/package_map.dart';
1717
import '../globals.dart';
18-
import '../ios/ios_workflow.dart';
18+
import '../ios/plist_parser.dart';
1919
import '../macos/xcode.dart';
2020
import '../resident_runner.dart';
2121
import '../runner/flutter_command.dart';
@@ -222,7 +222,7 @@ Future<void> validateBitcode() async {
222222
}
223223
final RunResult clangResult = await xcode.clang(<String>['--version']);
224224
final String clangVersion = clangResult.stdout.split('\n').first;
225-
final String engineClangVersion = iosWorkflow.getPlistValueFromFile(
225+
final String engineClangVersion = PlistParser.instance.getValueFromFile(
226226
fs.path.join(flutterFrameworkPath, 'Info.plist'),
227227
'ClangVersion',
228228
);

packages/flutter_tools/lib/src/doctor.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import 'fuchsia/fuchsia_workflow.dart';
2424
import 'globals.dart';
2525
import 'intellij/intellij.dart';
2626
import 'ios/ios_workflow.dart';
27-
import 'ios/plist_utils.dart';
27+
import 'ios/plist_parser.dart';
2828
import 'linux/linux_doctor.dart';
2929
import 'linux/linux_workflow.dart';
3030
import 'macos/cocoapods_validator.dart';
@@ -731,9 +731,9 @@ class IntelliJValidatorOnMac extends IntelliJValidator {
731731
String get version {
732732
if (_version == null) {
733733
final String plistFile = fs.path.join(installPath, 'Contents', 'Info.plist');
734-
_version = iosWorkflow.getPlistValueFromFile(
734+
_version = PlistParser.instance.getValueFromFile(
735735
plistFile,
736-
kCFBundleShortVersionStringKey,
736+
PlistParser.kCFBundleShortVersionStringKey,
737737
) ?? 'unknown';
738738
}
739739
return _version;

packages/flutter_tools/lib/src/ios/ios_workflow.dart

-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import '../base/context.dart';
66
import '../base/platform.dart';
77
import '../doctor.dart';
88
import '../macos/xcode.dart';
9-
import 'plist_utils.dart' as plist;
109

1110
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
1211

@@ -27,8 +26,4 @@ class IOSWorkflow implements Workflow {
2726

2827
@override
2928
bool get canListEmulators => false;
30-
31-
String getPlistValueFromFile(String path, String key) {
32-
return plist.getValueFromFile(path, key);
33-
}
3429
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2016 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../base/context.dart';
6+
import '../base/file_system.dart';
7+
import '../base/process.dart';
8+
import '../convert.dart';
9+
import '../globals.dart';
10+
11+
class PlistParser {
12+
const PlistParser();
13+
14+
static const String kCFBundleIdentifierKey = 'CFBundleIdentifier';
15+
static const String kCFBundleShortVersionStringKey = 'CFBundleShortVersionString';
16+
static const String kCFBundleExecutable = 'CFBundleExecutable';
17+
18+
static PlistParser get instance => context.get<PlistParser>() ?? const PlistParser();
19+
20+
/// Parses the plist file located at [plistFilePath] and returns the
21+
/// associated map of key/value property list pairs.
22+
///
23+
/// If [plistFilePath] points to a non-existent file or a file that's not a
24+
/// valid property list file, this will return an empty map.
25+
///
26+
/// The [plistFilePath] argument must not be null.
27+
Map<String, dynamic> parseFile(String plistFilePath) {
28+
assert(plistFilePath != null);
29+
const String executable = '/usr/bin/plutil';
30+
if (!fs.isFileSync(executable))
31+
throw const FileNotFoundException(executable);
32+
if (!fs.isFileSync(plistFilePath))
33+
return const <String, dynamic>{};
34+
35+
final String normalizedPlistPath = fs.path.absolute(plistFilePath);
36+
37+
try {
38+
final List<String> args = <String>[
39+
executable, '-convert', 'json', '-o', '-', normalizedPlistPath,
40+
];
41+
final String jsonContent = runCheckedSync(args);
42+
return json.decode(jsonContent);
43+
} catch (error) {
44+
printTrace('$error');
45+
return const <String, dynamic>{};
46+
}
47+
}
48+
49+
/// Parses the Plist file located at [plistFilePath] and returns the value
50+
/// that's associated with the specified [key] within the property list.
51+
///
52+
/// If [plistFilePath] points to a non-existent file or a file that's not a
53+
/// valid property list file, this will return null.
54+
///
55+
/// If [key] is not found in the property list, this will return null.
56+
///
57+
/// The [plistFilePath] and [key] arguments must not be null.
58+
String getValueFromFile(String plistFilePath, String key) {
59+
assert(key != null);
60+
final Map<String, dynamic> parsed = parseFile(plistFilePath);
61+
return parsed[key];
62+
}
63+
}

packages/flutter_tools/lib/src/ios/plist_utils.dart

-40
This file was deleted.

packages/flutter_tools/lib/src/ios/simulators.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import '../project.dart';
2424
import '../protocol_discovery.dart';
2525
import 'ios_workflow.dart';
2626
import 'mac.dart';
27-
import 'plist_utils.dart';
27+
import 'plist_parser.dart';
2828

2929
const String _xcrunPath = '/usr/bin/xcrun';
3030
const String iosSimulatorId = 'apple_ios_simulator';
@@ -379,7 +379,7 @@ class IOSSimulator extends Device {
379379
// parsing the xcodeproj or configuration files.
380380
// See https://github.com/flutter/flutter/issues/31037 for more information.
381381
final String plistPath = fs.path.join(package.simulatorBundlePath, 'Info.plist');
382-
final String bundleIdentifier = iosWorkflow.getPlistValueFromFile(plistPath, kCFBundleIdentifierKey);
382+
final String bundleIdentifier = PlistParser.instance.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
383383

384384
await SimControl.instance.launch(id, bundleIdentifier, args);
385385
} catch (error) {

packages/flutter_tools/lib/src/macos/application_package.dart

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import '../application_package.dart';
88
import '../base/file_system.dart';
99
import '../build_info.dart';
1010
import '../globals.dart';
11-
import '../ios/plist_utils.dart' as plist;
11+
import '../ios/plist_parser.dart';
1212
import '../project.dart';
1313

1414
/// Tests whether a [FileSystemEntity] is an macOS bundle directory
@@ -65,8 +65,9 @@ abstract class MacOSApp extends ApplicationPackage {
6565
printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
6666
return null;
6767
}
68-
final String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey);
69-
final String executableName = plist.getValueFromFile(plistPath, plist.kCFBundleExecutable);
68+
final Map<String, dynamic> propertyValues = PlistParser.instance.parseFile(plistPath);
69+
final String id = propertyValues[PlistParser.kCFBundleIdentifierKey];
70+
final String executableName = propertyValues[PlistParser.kCFBundleExecutable];
7071
if (id == null) {
7172
printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier');
7273
return null;

packages/flutter_tools/lib/src/project.dart

+10-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import 'cache.dart';
1717
import 'features.dart';
1818
import 'flutter_manifest.dart';
1919
import 'globals.dart';
20-
import 'ios/ios_workflow.dart';
21-
import 'ios/plist_utils.dart' as plist;
20+
import 'ios/plist_parser.dart';
2221
import 'ios/xcodeproj.dart' as xcode;
2322
import 'plugins.dart';
2423
import 'template.dart';
@@ -361,10 +360,15 @@ class IosProject implements XcodeBasedProject {
361360
/// The product bundle identifier of the host app, or null if not set or if
362361
/// iOS tooling needed to read it is not installed.
363362
String get productBundleIdentifier {
364-
final String fromPlist = iosWorkflow.getPlistValueFromFile(
365-
hostInfoPlist.path,
366-
plist.kCFBundleIdentifierKey,
367-
);
363+
String fromPlist;
364+
try {
365+
fromPlist = PlistParser.instance.getValueFromFile(
366+
hostInfoPlist.path,
367+
PlistParser.kCFBundleIdentifierKey,
368+
);
369+
} on FileNotFoundException {
370+
// iOS tooling not found; likely not running OSX; let [fromPlist] be null
371+
}
368372
if (fromPlist != null && !fromPlist.contains('\$')) {
369373
// Info.plist has no build variables in product bundle ID.
370374
return fromPlist;

0 commit comments

Comments
 (0)