Skip to content

Commit ace5442

Browse files
authored
Add observatory Bonjour service to built iOS Info.plist bundle (flutter#65138)
1 parent 020215b commit ace5442

File tree

5 files changed

+352
-97
lines changed

5 files changed

+352
-97
lines changed

dev/devicelab/bin/tasks/ios_content_validation_test.dart

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import 'package:path/path.dart' as path;
1414
Future<void> main() async {
1515
await task(() async {
1616
try {
17-
bool foundProjectName = false;
18-
bool bitcode = false;
1917
await runProjectTest((FlutterProject flutterProject) async {
2018
section('Build app with with --obfuscate');
2119
await inDirectory(flutterProject.rootPath, () async {
@@ -52,6 +50,13 @@ Future<void> main() async {
5250
fail('Failed to produce expected output at ${outputAppFrameworkBinary.path}');
5351
}
5452

53+
if (await dartObservatoryBonjourServiceFound(outputAppPath)) {
54+
throw TaskResult.failure('Release bundle has unexpected NSBonjourServices');
55+
}
56+
if (await localNetworkUsageFound(outputAppPath)) {
57+
throw TaskResult.failure('Release bundle has unexpected NSLocalNetworkUsageDescription');
58+
}
59+
5560
section('Validate obfuscation');
5661

5762
// Verify that an identifier from the Dart project code is not present
@@ -63,11 +68,11 @@ Future<void> main() async {
6368
canFail: true,
6469
);
6570
if (response.trim().contains('matches')) {
66-
foundProjectName = true;
71+
throw TaskResult.failure('Found project name in obfuscated dart library');
6772
}
6873
});
6974

70-
section('Validate bitcode');
75+
section('Validate release contents');
7176

7277
final Directory outputFlutterFramework = Directory(path.join(
7378
flutterProject.rootPath,
@@ -83,7 +88,13 @@ Future<void> main() async {
8388
if (!outputFlutterFrameworkBinary.existsSync()) {
8489
fail('Failed to produce expected output at ${outputFlutterFrameworkBinary.path}');
8590
}
86-
bitcode = await containsBitcode(outputFlutterFrameworkBinary.path);
91+
92+
// Archiving should contain a bitcode blob, but not building in release.
93+
// This mimics Xcode behavior and present a developer from having to install a
94+
// 300+MB app to test devices.
95+
if (await containsBitcode(outputFlutterFrameworkBinary.path)) {
96+
throw TaskResult.failure('Bitcode present in Flutter.framework');
97+
}
8798

8899
section('Xcode backend script');
89100

@@ -101,7 +112,7 @@ Future<void> main() async {
101112
'xcode_backend.sh'
102113
);
103114

104-
// Simulate a commonly Xcode build setting misconfiguration
115+
// Simulate a common Xcode build setting misconfiguration
105116
// where FLUTTER_APPLICATION_PATH is missing
106117
final int result = await exec(
107118
xcodeBackendPath,
@@ -111,6 +122,7 @@ Future<void> main() async {
111122
'TARGET_BUILD_DIR': buildPath,
112123
'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks',
113124
'VERBOSE_SCRIPT_LOGGING': '1',
125+
'FLUTTER_BUILD_MODE': 'release',
114126
'ACTION': 'install', // Skip bitcode stripping since we just checked that above.
115127
},
116128
);
@@ -126,17 +138,35 @@ Future<void> main() async {
126138
if (!outputAppFrameworkBinary.existsSync()) {
127139
fail('Failed to re-embed ${outputAppFrameworkBinary.path}');
128140
}
129-
});
130141

131-
if (foundProjectName) {
132-
return TaskResult.failure('Found project name in obfuscated dart library');
133-
}
134-
// Archiving should contain a bitcode blob, but not building in release.
135-
// This mimics Xcode behavior and present a developer from having to install a
136-
// 300+MB app to test devices.
137-
if (bitcode) {
138-
return TaskResult.failure('Bitcode present in Flutter.framework');
139-
}
142+
section('Clean build');
143+
144+
await inDirectory(flutterProject.rootPath, () async {
145+
await flutter('clean');
146+
});
147+
148+
section('Validate debug contents');
149+
150+
await inDirectory(flutterProject.rootPath, () async {
151+
await flutter('build', options: <String>[
152+
'ios',
153+
'--debug',
154+
'--no-codesign',
155+
]);
156+
});
157+
158+
// Debug should also not contain bitcode.
159+
if (await containsBitcode(outputFlutterFrameworkBinary.path)) {
160+
throw TaskResult.failure('Bitcode present in Flutter.framework');
161+
}
162+
163+
if (!await dartObservatoryBonjourServiceFound(outputAppPath)) {
164+
throw TaskResult.failure('Debug bundle is missing NSBonjourServices');
165+
}
166+
if (!await localNetworkUsageFound(outputAppPath)) {
167+
throw TaskResult.failure('Debug bundle is missing NSLocalNetworkUsageDescription');
168+
}
169+
});
140170

141171
return TaskResult.success(null);
142172
} on TaskResult catch (taskResult) {

dev/devicelab/lib/framework/ios.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import 'dart:async';
66
import 'dart:convert';
77

8+
import 'package:path/path.dart' as path;
9+
810
import 'utils.dart';
911

1012
typedef SimulatorFunction = Future<void> Function(String deviceId);
@@ -102,6 +104,40 @@ Future<bool> containsBitcode(String pathToBinary) async {
102104
return !emptyBitcodeMarkerFound;
103105
}
104106

107+
Future<bool> dartObservatoryBonjourServiceFound(String appBundlePath) async =>
108+
(await eval(
109+
'plutil',
110+
<String>[
111+
'-extract',
112+
'NSBonjourServices',
113+
'xml1',
114+
'-o',
115+
'-',
116+
path.join(
117+
appBundlePath,
118+
'Info.plist',
119+
),
120+
],
121+
canFail: true,
122+
)).contains('_dartobservatory._tcp');
123+
124+
Future<bool> localNetworkUsageFound(String appBundlePath) async =>
125+
await exec(
126+
'plutil',
127+
<String>[
128+
'-extract',
129+
'NSLocalNetworkUsageDescription',
130+
'xml1',
131+
'-o',
132+
'-',
133+
path.join(
134+
appBundlePath,
135+
'Info.plist',
136+
),
137+
],
138+
canFail: true,
139+
) == 0;
140+
105141
/// Creates and boots a new simulator, passes the new simulator's identifier to
106142
/// `testFunction`.
107143
///

packages/flutter_tools/bin/xcode_backend.sh

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,32 @@ AssertExists() {
3838
return 0
3939
}
4040

41+
ParseFlutterBuildMode() {
42+
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
43+
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
44+
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
45+
local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
46+
47+
case "$build_mode" in
48+
*release*) build_mode="release";;
49+
*profile*) build_mode="profile";;
50+
*debug*) build_mode="debug";;
51+
*)
52+
EchoError "========================================================================"
53+
EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
54+
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
55+
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
56+
EchoError "If that is not set, the CONFIGURATION environment variable is used."
57+
EchoError ""
58+
EchoError "You can fix this by either adding an appropriately named build"
59+
EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
60+
EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
61+
EchoError "========================================================================"
62+
exit -1;;
63+
esac
64+
echo "${build_mode}"
65+
}
66+
4167
BuildApp() {
4268
local project_path="${SOURCE_ROOT}/.."
4369
if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
@@ -72,24 +98,12 @@ BuildApp() {
7298
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
7399
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
74100
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
75-
local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
101+
local build_mode="$(ParseFlutterBuildMode)"
76102
local artifact_variant="unknown"
77103
case "$build_mode" in
78-
*release*) build_mode="release"; artifact_variant="ios-release";;
79-
*profile*) build_mode="profile"; artifact_variant="ios-profile";;
80-
*debug*) build_mode="debug"; artifact_variant="ios";;
81-
*)
82-
EchoError "========================================================================"
83-
EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
84-
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
85-
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
86-
EchoError "If that is not set, the CONFIGURATION environment variable is used."
87-
EchoError ""
88-
EchoError "You can fix this by either adding an appropriately named build"
89-
EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
90-
EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
91-
EchoError "========================================================================"
92-
exit -1;;
104+
release ) artifact_variant="ios-release";;
105+
profile ) artifact_variant="ios-profile";;
106+
debug ) artifact_variant="ios";;
93107
esac
94108

95109
# Warn the user if not archiving (ACTION=install) in release mode.
@@ -127,7 +141,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr
127141
fi
128142

129143
local bitcode_flag=""
130-
if [[ $ENABLE_BITCODE == "YES" ]]; then
144+
if [[ "$ENABLE_BITCODE" == "YES" ]]; then
131145
bitcode_flag="true"
132146
fi
133147

@@ -306,6 +320,36 @@ EmbedFlutterFrameworks() {
306320
RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/App.framework/App"
307321
RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
308322
fi
323+
324+
AddObservatoryBonjourService
325+
}
326+
327+
# Add the observatory publisher Bonjour service to the produced app bundle Info.plist.
328+
AddObservatoryBonjourService() {
329+
local build_mode="$(ParseFlutterBuildMode)"
330+
# Debug and profile only.
331+
if [[ "${build_mode}" == "release" ]]; then
332+
return
333+
fi
334+
local built_products_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
335+
336+
if [[ ! -f "${built_products_plist}" ]]; then
337+
EchoError "error: ${INFOPLIST_PATH} does not exist. The Flutter \"Thin Binary\" build phase must run after \"Copy Bundle Resources\"."
338+
exit -1
339+
fi
340+
# If there are already NSBonjourServices specified by the app (uncommon), insert the observatory service name to the existing list.
341+
if plutil -extract NSBonjourServices xml1 -o - "${built_products_plist}"; then
342+
RunCommand plutil -insert NSBonjourServices.0 -string "_dartobservatory._tcp" "${built_products_plist}"
343+
else
344+
# Otherwise, add the NSBonjourServices key and observatory service name.
345+
RunCommand plutil -insert NSBonjourServices -json "[\"_dartobservatory._tcp\"]" "${built_products_plist}"
346+
fi
347+
348+
# Don't override the local network description the Flutter app developer specified (uncommon).
349+
# This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
350+
if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
351+
RunCommand plutil -insert NSLocalNetworkUsageDescription -string "Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds." "${built_products_plist}"
352+
fi
309353
}
310354

311355
EmbedAndThinFrameworks() {
@@ -328,5 +372,8 @@ else
328372
EmbedFlutterFrameworks ;;
329373
"embed_and_thin")
330374
EmbedAndThinFrameworks ;;
375+
"test_observatory_bonjour_service")
376+
# Exposed for integration testing only.
377+
AddObservatoryBonjourService ;;
331378
esac
332379
fi

packages/flutter_tools/test/general.shard/ios/xcode_backend_test.dart

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)