1// Copyright 2014 The Flutter 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
5import 'package:flutter_tools/src/base/error_handling_io.dart';
6import 'package:flutter_tools/src/base/file_system.dart';
7import 'package:flutter_tools/src/base/io.dart';
8
9import '../src/common.dart';
10import 'test_utils.dart';
11
12void main() {
13 const customLLDBInitFileSchemeSetting =
14 r'customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"';
15 test(
16 'Ensure lldb is added to Xcode project',
17 () async {
18 final Directory workingDirectory = fileSystem.systemTempDirectory.createTempSync(
19 'lldb_test.',
20 );
21 try {
22 final String workingDirectoryPath = workingDirectory.path;
23 const appName = 'lldb_test';
24
25 final ProcessResult createResult = await processManager.run(<String>[
26 flutterBin,
27 ...getLocalEngineArguments(),
28 'create',
29 '--org',
30 'io.flutter.devicelab',
31 appName,
32 '--platforms=ios',
33 ], workingDirectory: workingDirectory.path);
34 expect(
35 createResult.exitCode,
36 0,
37 reason:
38 'Failed to create app: \n'
39 'stdout: \n${createResult.stdout}\n'
40 'stderr: \n${createResult.stderr}\n',
41 );
42
43 final String appDirectoryPath = fileSystem.path.join(workingDirectoryPath, appName);
44
45 final File schemeFile = fileSystem
46 .directory(appDirectoryPath)
47 .childDirectory('ios')
48 .childDirectory('Runner.xcodeproj')
49 .childDirectory('xcshareddata')
50 .childDirectory('xcschemes')
51 .childFile('Runner.xcscheme');
52 expect(schemeFile.existsSync(), isTrue);
53
54 // Remove customLLDBInitFile from the scheme so we can validate it
55 // gets re-added if missing.
56 expect(schemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
57 schemeFile.writeAsStringSync(
58 schemeFile.readAsStringSync().replaceAll(customLLDBInitFileSchemeSetting, ''),
59 );
60
61 final File lldbInitFile = fileSystem
62 .directory(appDirectoryPath)
63 .childDirectory('ios')
64 .childDirectory('Flutter')
65 .childDirectory('ephemeral')
66 .childFile('flutter_lldbinit');
67 expect(lldbInitFile.existsSync(), isTrue);
68
69 final File lldbPythonFile = fileSystem
70 .directory(appDirectoryPath)
71 .childDirectory('ios')
72 .childDirectory('Flutter')
73 .childDirectory('ephemeral')
74 .childFile('flutter_lldb_helper.py');
75 expect(lldbPythonFile.existsSync(), isTrue);
76
77 // Delete LLDB files so we can verify they get re-added if missing.
78 lldbInitFile.deleteSync();
79 lldbPythonFile.deleteSync();
80
81 final ProcessResult buildResult = await processManager.run(<String>[
82 flutterBin,
83 ...getLocalEngineArguments(),
84 'build',
85 'ios',
86 ], workingDirectory: appDirectoryPath);
87 expect(
88 buildResult.exitCode,
89 0,
90 reason:
91 'Failed to build the app: \n'
92 'stdout: \n${buildResult.stdout}\n'
93 'stderr: \n${buildResult.stderr}\n',
94 );
95
96 expect(schemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
97 expect(lldbInitFile.existsSync(), isTrue);
98 expect(lldbPythonFile.existsSync(), isTrue);
99 } finally {
100 ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true);
101 }
102 },
103 skip: !platform.isMacOS, // [intended] Can only build on macOS.
104 );
105
106 test(
107 'Ensure lldb is added to Xcode project when using flavor',
108 () async {
109 final Directory workingDirectory = fileSystem.systemTempDirectory.createTempSync(
110 'lldb_test.',
111 );
112 try {
113 final String workingDirectoryPath = workingDirectory.path;
114 const appName = 'lldb_test';
115 const flavor = 'vanilla';
116
117 final ProcessResult createResult = await processManager.run(<String>[
118 flutterBin,
119 ...getLocalEngineArguments(),
120 'create',
121 '--org',
122 'io.flutter.devicelab',
123 appName,
124 '--platforms=ios',
125 ], workingDirectory: workingDirectory.path);
126 expect(
127 createResult.exitCode,
128 0,
129 reason:
130 'Failed to create app: \n'
131 'stdout: \n${createResult.stdout}\n'
132 'stderr: \n${createResult.stderr}\n',
133 );
134
135 final String appDirectoryPath = fileSystem.path.join(workingDirectoryPath, appName);
136 final File schemeFile = fileSystem
137 .directory(appDirectoryPath)
138 .childDirectory('ios')
139 .childDirectory('Runner.xcodeproj')
140 .childDirectory('xcshareddata')
141 .childDirectory('xcschemes')
142 .childFile('Runner.xcscheme');
143 expect(schemeFile.existsSync(), isTrue);
144
145 final File pbxprojFile = fileSystem
146 .directory(appDirectoryPath)
147 .childDirectory('ios')
148 .childDirectory('Runner.xcodeproj')
149 .childFile('project.pbxproj');
150 expect(pbxprojFile.existsSync(), isTrue);
151
152 // Create flavor
153 final File flavorSchemeFile = fileSystem
154 .directory(appDirectoryPath)
155 .childDirectory('ios')
156 .childDirectory('Runner.xcodeproj')
157 .childDirectory('xcshareddata')
158 .childDirectory('xcschemes')
159 .childFile('$flavor.xcscheme');
160 flavorSchemeFile.createSync(recursive: true);
161 flavorSchemeFile.writeAsStringSync(schemeFile.readAsStringSync());
162
163 String pbxprojContents = pbxprojFile.readAsStringSync();
164 pbxprojContents = pbxprojContents.replaceAll('97C147071CF9000F007C117D /* Release */,', '''
16597C147071CF9000F007C117D /* Release */,
16678624EC12D71262400FF7985 /* Release-vanilla */,
167''');
168 pbxprojContents = pbxprojContents.replaceAll('97C147041CF9000F007C117D /* Release */,', '''
16997C147041CF9000F007C117D /* Release */,
17078624EC02D71262400FF7985 /* Release-vanilla */,
171''');
172
173 pbxprojContents = pbxprojContents.replaceAll(
174 '/* Begin XCBuildConfiguration section */',
175 r'''
176/* Begin XCBuildConfiguration section */
17778624EC12D71262400FF7985 /* Release-vanilla */ = {
178 isa = XCBuildConfiguration;
179 baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
180 buildSettings = {
181 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
182 CLANG_ENABLE_MODULES = YES;
183 CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
184 ENABLE_BITCODE = NO;
185 INFOPLIST_FILE = Runner/Info.plist;
186 LD_RUNPATH_SEARCH_PATHS = (
187 "$(inherited)",
188 "@executable_path/Frameworks",
189 );
190 PRODUCT_BUNDLE_IDENTIFIER = com.example.lldb_test;
191 PRODUCT_NAME = "$(TARGET_NAME)";
192 SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
193 SWIFT_VERSION = 5.0;
194 VERSIONING_SYSTEM = "apple-generic";
195 };
196 name = "Release-vanilla";
197 };
198 78624EC02D71262400FF7985 /* Release-vanilla */ = {
199 isa = XCBuildConfiguration;
200 buildSettings = {
201 ALWAYS_SEARCH_USER_PATHS = NO;
202 ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
203 CLANG_ANALYZER_NONNULL = YES;
204 CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
205 CLANG_CXX_LIBRARY = "libc++";
206 CLANG_ENABLE_MODULES = YES;
207 CLANG_ENABLE_OBJC_ARC = YES;
208 CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
209 CLANG_WARN_BOOL_CONVERSION = YES;
210 CLANG_WARN_COMMA = YES;
211 CLANG_WARN_CONSTANT_CONVERSION = YES;
212 CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
213 CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
214 CLANG_WARN_EMPTY_BODY = YES;
215 CLANG_WARN_ENUM_CONVERSION = YES;
216 CLANG_WARN_INFINITE_RECURSION = YES;
217 CLANG_WARN_INT_CONVERSION = YES;
218 CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
219 CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
220 CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
221 CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
222 CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
223 CLANG_WARN_STRICT_PROTOTYPES = YES;
224 CLANG_WARN_SUSPICIOUS_MOVE = YES;
225 CLANG_WARN_UNREACHABLE_CODE = YES;
226 CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
227 COPY_PHASE_STRIP = NO;
228 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
229 ENABLE_NS_ASSERTIONS = NO;
230 ENABLE_STRICT_OBJC_MSGSEND = YES;
231 ENABLE_USER_SCRIPT_SANDBOXING = NO;
232 GCC_C_LANGUAGE_STANDARD = gnu99;
233 GCC_NO_COMMON_BLOCKS = YES;
234 GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
235 GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
236 GCC_WARN_UNDECLARED_SELECTOR = YES;
237 GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
238 GCC_WARN_UNUSED_FUNCTION = YES;
239 GCC_WARN_UNUSED_VARIABLE = YES;
240 IPHONEOS_DEPLOYMENT_TARGET = 12.0;
241 MTL_ENABLE_DEBUG_INFO = NO;
242 SDKROOT = iphoneos;
243 SUPPORTED_PLATFORMS = iphoneos;
244 SWIFT_COMPILATION_MODE = wholemodule;
245 SWIFT_OPTIMIZATION_LEVEL = "-O";
246 TARGETED_DEVICE_FAMILY = "1,2";
247 VALIDATE_PRODUCT = YES;
248 };
249 name = "Release-vanilla";
250 };
251''',
252 );
253 pbxprojFile.writeAsStringSync(pbxprojContents);
254
255 // Remove customLLDBInitFile from the flavor's scheme so we can validate
256 // it gets re-added later.
257 expect(flavorSchemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
258 flavorSchemeFile.writeAsStringSync(
259 flavorSchemeFile.readAsStringSync().replaceAll(customLLDBInitFileSchemeSetting, ''),
260 );
261
262 final ProcessResult buildResult = await processManager.run(<String>[
263 flutterBin,
264 ...getLocalEngineArguments(),
265 'build',
266 'ios',
267 '--config-only',
268 '--flavor',
269 flavor,
270 ], workingDirectory: appDirectoryPath);
271 expect(
272 buildResult.exitCode,
273 0,
274 reason:
275 'Failed to build config for the app: \n'
276 'stdout: \n${buildResult.stdout}\n'
277 'stderr: \n${buildResult.stderr}\n',
278 );
279
280 expect(flavorSchemeFile.readAsStringSync(), contains(customLLDBInitFileSchemeSetting));
281
282 final File lldbInitFile = fileSystem
283 .directory(appDirectoryPath)
284 .childDirectory('ios')
285 .childDirectory('Flutter')
286 .childDirectory('ephemeral')
287 .childFile('flutter_lldbinit');
288 expect(lldbInitFile.existsSync(), isTrue);
289
290 final File lldbPythonFile = fileSystem
291 .directory(appDirectoryPath)
292 .childDirectory('ios')
293 .childDirectory('Flutter')
294 .childDirectory('ephemeral')
295 .childFile('flutter_lldb_helper.py');
296 expect(lldbPythonFile.existsSync(), isTrue);
297 } finally {
298 ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true);
299 }
300 },
301 skip: !platform.isMacOS, // [intended] Can only build on macOS.
302 );
303}
304