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
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index f21dffd..ac9630b 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -6,7 +6,6 @@
import '../base/platform.dart';
import '../doctor.dart';
import '../macos/xcode.dart';
-import 'plist_utils.dart' as plist;
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
@@ -27,8 +26,4 @@
@override
bool get canListEmulators => false;
-
- String getPlistValueFromFile(String path, String key) {
- return plist.getValueFromFile(path, key);
- }
}
diff --git a/packages/flutter_tools/lib/src/ios/plist_parser.dart b/packages/flutter_tools/lib/src/ios/plist_parser.dart
new file mode 100644
index 0000000..8e8beeb
--- /dev/null
+++ b/packages/flutter_tools/lib/src/ios/plist_parser.dart
@@ -0,0 +1,63 @@
+// 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];
+ }
+}
diff --git a/packages/flutter_tools/lib/src/ios/plist_utils.dart b/packages/flutter_tools/lib/src/ios/plist_utils.dart
deleted file mode 100644
index 0a67279..0000000
--- a/packages/flutter_tools/lib/src/ios/plist_utils.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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;
- }
-}
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 043f651..ad46f6d 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -24,7 +24,7 @@
import '../protocol_discovery.dart';
import 'ios_workflow.dart';
import 'mac.dart';
-import 'plist_utils.dart';
+import 'plist_parser.dart';
const String _xcrunPath = '/usr/bin/xcrun';
const String iosSimulatorId = 'apple_ios_simulator';
@@ -379,7 +379,7 @@
// parsing the xcodeproj or configuration files.
// See https://github.com/flutter/flutter/issues/31037 for more information.
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);
} catch (error) {