blob: 26df37dd982acd6c882bc7855366d70ecdc499b7 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +01002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +02005import 'package:meta/meta.dart';
Dan Field33aa4572020-07-07 16:40:33 -07006import 'package:xml/xml.dart';
Jonah Williamsb06a7092019-02-05 18:35:20 -08007import 'package:yaml/yaml.dart';
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +02008
Balvinder Singh Gambhir3dde3e02020-09-15 23:51:54 +05309import '../src/convert.dart';
Emmanuel Garcia175b3722019-10-31 13:19:15 -070010import 'android/gradle_utils.dart' as gradle;
Jonah Williams523ac7b2019-12-09 21:18:09 -080011import 'artifacts.dart';
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020012import 'base/common.dart';
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +020013import 'base/file_system.dart';
Jonah Williams8ed40dd2020-04-13 18:55:01 -070014import 'base/logger.dart';
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +020015import 'build_info.dart';
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +020016import 'bundle.dart' as bundle;
Jonah Williams3fedb8c2019-07-22 15:34:03 -070017import 'features.dart';
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +020018import 'flutter_manifest.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080019import 'globals.dart' as globals;
Todd Volkertc22ce952019-08-16 17:10:07 -070020import 'ios/plist_parser.dart';
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +020021import 'ios/xcodeproj.dart' as xcode;
Jenn Magdere110ca72020-07-09 16:56:02 -070022import 'ios/xcodeproj.dart';
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080023import 'platform_plugins.dart';
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +020024import 'plugins.dart';
25import 'template.dart';
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +010026
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070027class FlutterProjectFactory {
Jonah Williams8ed40dd2020-04-13 18:55:01 -070028 FlutterProjectFactory({
29 @required Logger logger,
30 @required FileSystem fileSystem,
31 }) : _logger = logger,
32 _fileSystem = fileSystem;
33
34 final Logger _logger;
35 final FileSystem _fileSystem;
Zachary Andersonb7c714e2019-08-28 10:03:53 -070036
Emmanuel Garciaa15a81b2019-12-17 14:10:36 -080037 @visibleForTesting
38 final Map<String, FlutterProject> projects =
Zachary Andersonb7c714e2019-08-28 10:03:53 -070039 <String, FlutterProject>{};
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070040
41 /// Returns a [FlutterProject] view of the given directory or a ToolExit error,
42 /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
43 FlutterProject fromDirectory(Directory directory) {
44 assert(directory != null);
Jonah Williams8ed40dd2020-04-13 18:55:01 -070045 return projects.putIfAbsent(directory.path, () {
Zachary Andersonb7c714e2019-08-28 10:03:53 -070046 final FlutterManifest manifest = FlutterProject._readManifest(
47 directory.childFile(bundle.defaultManifestPath).path,
Jonah Williams8ed40dd2020-04-13 18:55:01 -070048 logger: _logger,
49 fileSystem: _fileSystem,
Zachary Andersonb7c714e2019-08-28 10:03:53 -070050 );
51 final FlutterManifest exampleManifest = FlutterProject._readManifest(
52 FlutterProject._exampleDirectory(directory)
53 .childFile(bundle.defaultManifestPath)
54 .path,
Jonah Williams8ed40dd2020-04-13 18:55:01 -070055 logger: _logger,
56 fileSystem: _fileSystem,
Zachary Andersonb7c714e2019-08-28 10:03:53 -070057 );
58 return FlutterProject(directory, manifest, exampleManifest);
59 });
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070060 }
61}
62
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +010063/// Represents the contents of a Flutter project at the specified [directory].
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020064///
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +020065/// [FlutterManifest] information is read from `pubspec.yaml` and
66/// `example/pubspec.yaml` files on construction of a [FlutterProject] instance.
67/// The constructed instance carries an immutable snapshot representation of the
68/// presence and content of those files. Accordingly, [FlutterProject] instances
69/// should be discarded upon changes to the `pubspec.yaml` files, but can be
70/// used across changes to other files, as no other file-level information is
71/// cached.
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +010072class FlutterProject {
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +020073 @visibleForTesting
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020074 FlutterProject(this.directory, this.manifest, this._exampleManifest)
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +010075 : assert(directory != null),
76 assert(manifest != null),
77 assert(_exampleManifest != null);
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +020078
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070079 /// Returns a [FlutterProject] view of the given directory or a ToolExit error,
80 /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
Jenn Magderce40fba2020-03-27 16:21:45 -070081 static FlutterProject fromDirectory(Directory directory) => globals.projectFactory.fromDirectory(directory);
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020082
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070083 /// Returns a [FlutterProject] view of the current directory or a ToolExit error,
84 /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
Shi-Hao Hongfd22fc32020-08-31 13:19:41 +080085 static FlutterProject current() => globals.projectFactory.fromDirectory(globals.fs.currentDirectory);
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020086
Lau Ching Juncfe4d0d2019-06-26 13:29:53 -070087 /// Returns a [FlutterProject] view of the given directory or a ToolExit error,
88 /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
Shi-Hao Hongfd22fc32020-08-31 13:19:41 +080089 static FlutterProject fromPath(String path) => globals.projectFactory.fromDirectory(globals.fs.directory(path));
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +010090
91 /// The location of this project.
92 final Directory directory;
93
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020094 /// The manifest of this project.
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020095 final FlutterManifest manifest;
96
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +020097 /// The manifest of the example sub-project of this project.
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +020098 final FlutterManifest _exampleManifest;
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +020099
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200100 /// The set of organization names found in this project as
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100101 /// part of iOS product bundle identifier, Android application ID, or
102 /// Gradle group ID.
Zachary Anderson8a33d242019-09-16 07:51:50 -0700103 Future<Set<String>> get organizationNames async {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200104 final List<String> candidates = <String>[
Jenn Magder9c87b322020-05-11 11:56:44 -0700105 // Don't require iOS build info, this method is only
106 // used during create as best-effort, use the
107 // default target bundle identifier.
Jenn Magdere110ca72020-07-09 16:56:02 -0700108 if (ios.existsSync())
109 await ios.productBundleIdentifier(null),
110 if (android.existsSync()) ...<String>[
111 android.applicationId,
112 android.group,
113 ],
114 if (example.android.existsSync())
115 example.android.applicationId,
116 if (example.ios.existsSync())
117 await example.ios.productBundleIdentifier(null),
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200118 ];
James D. Lin566c1d12020-04-21 22:09:50 -0700119 return Set<String>.of(candidates
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200120 .map<String>(_organizationNameFromPackageName)
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +0200121 .where((String name) => name != null));
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100122 }
123
124 String _organizationNameFromPackageName(String packageName) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700125 if (packageName != null && 0 <= packageName.lastIndexOf('.')) {
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100126 return packageName.substring(0, packageName.lastIndexOf('.'));
Zachary Andersone2340c62019-09-13 14:51:35 -0700127 }
128 return null;
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100129 }
130
131 /// The iOS sub project of this project.
Zachary Anderson8841afe2019-05-14 10:59:23 -0700132 IosProject _ios;
133 IosProject get ios => _ios ??= IosProject.fromFlutter(this);
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100134
135 /// The Android sub project of this project.
Zachary Anderson8841afe2019-05-14 10:59:23 -0700136 AndroidProject _android;
137 AndroidProject get android => _android ??= AndroidProject._(this);
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200138
Jonah Williams6f5f0372019-02-26 16:58:24 -0800139 /// The web sub project of this project.
Zachary Anderson8841afe2019-05-14 10:59:23 -0700140 WebProject _web;
141 WebProject get web => _web ??= WebProject._(this);
Jonah Williams6f5f0372019-02-26 16:58:24 -0800142
Zachary Anderson8841afe2019-05-14 10:59:23 -0700143 /// The MacOS sub project of this project.
144 MacOSProject _macos;
145 MacOSProject get macos => _macos ??= MacOSProject._(this);
Jonah Williams86c938b2019-04-17 12:16:55 -0700146
Zachary Anderson8841afe2019-05-14 10:59:23 -0700147 /// The Linux sub project of this project.
148 LinuxProject _linux;
149 LinuxProject get linux => _linux ??= LinuxProject._(this);
Jonah Williams86c938b2019-04-17 12:16:55 -0700150
Zachary Anderson8841afe2019-05-14 10:59:23 -0700151 /// The Windows sub project of this project.
152 WindowsProject _windows;
153 WindowsProject get windows => _windows ??= WindowsProject._(this);
154
155 /// The Fuchsia sub project of this project.
156 FuchsiaProject _fuchsia;
157 FuchsiaProject get fuchsia => _fuchsia ??= FuchsiaProject._(this);
Jonah Williams86c938b2019-04-17 12:16:55 -0700158
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200159 /// The `pubspec.yaml` file of this project.
160 File get pubspecFile => directory.childFile('pubspec.yaml');
161
162 /// The `.packages` file of this project.
163 File get packagesFile => directory.childFile('.packages');
164
Jenn Magderce40fba2020-03-27 16:21:45 -0700165 /// The `.metadata` file of this project.
166 File get metadataFile => directory.childFile('.metadata');
167
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200168 /// The `.flutter-plugins` file of this project.
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +0200169 File get flutterPluginsFile => directory.childFile('.flutter-plugins');
170
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800171 /// The `.flutter-plugins-dependencies` file of this project,
172 /// which contains the dependencies each plugin depends on.
173 File get flutterPluginsDependenciesFile => directory.childFile('.flutter-plugins-dependencies');
174
Jonah Williams1237ee82019-01-24 15:59:55 -0800175 /// The `.dart-tool` directory of this project.
176 Directory get dartTool => directory.childDirectory('.dart_tool');
177
Jonah Williams67cf2152019-02-14 23:17:16 -0800178 /// The directory containing the generated code for this project.
179 Directory get generated => directory
Jonah Williamsa2d349c2019-03-07 11:02:42 -0800180 .absolute
Jonah Williams67cf2152019-02-14 23:17:16 -0800181 .childDirectory('.dart_tool')
182 .childDirectory('build')
183 .childDirectory('generated')
184 .childDirectory(manifest.appName);
185
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +0200186 /// The example sub-project of this project.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200187 FlutterProject get example => FlutterProject(
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200188 _exampleDirectory(directory),
189 _exampleManifest,
Jonah Williams8ed40dd2020-04-13 18:55:01 -0700190 FlutterManifest.empty(logger: globals.logger),
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200191 );
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200192
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700193 /// True if this project is a Flutter module project.
194 bool get isModule => manifest.isModule;
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200195
Nolan Scobie43c1b342020-08-06 19:18:52 -0400196 /// True if the Flutter project is using the AndroidX support library.
Josh Burtond0e45a22019-06-01 13:33:02 +1200197 bool get usesAndroidX => manifest.usesAndroidX;
198
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700199 /// True if this project has an example application.
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200200 bool get hasExampleApp => _exampleDirectory(directory).existsSync();
Mikkel Nygaard Ravn20004352018-02-16 10:17:28 +0100201
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +0200202 /// The directory that will contain the example if an example exists.
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200203 static Directory _exampleDirectory(Directory directory) => directory.childDirectory('example');
Mikkel Nygaard Ravn20004352018-02-16 10:17:28 +0100204
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +0200205 /// Reads and validates the `pubspec.yaml` file at [path], asynchronously
206 /// returning a [FlutterManifest] representation of the contents.
207 ///
208 /// Completes with an empty [FlutterManifest], if the file does not exist.
209 /// Completes with a ToolExit on validation error.
Jonah Williams8ed40dd2020-04-13 18:55:01 -0700210 static FlutterManifest _readManifest(String path, {
211 @required Logger logger,
212 @required FileSystem fileSystem,
213 }) {
Zachary Anderson13382f42019-07-12 23:24:04 -0700214 FlutterManifest manifest;
215 try {
Jonah Williams8ed40dd2020-04-13 18:55:01 -0700216 manifest = FlutterManifest.createFromPath(
217 path,
218 logger: logger,
219 fileSystem: fileSystem,
220 );
Zachary Anderson13382f42019-07-12 23:24:04 -0700221 } on YamlException catch (e) {
Jonah Williams8ed40dd2020-04-13 18:55:01 -0700222 logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
223 logger.printError('$e');
Zachary Anderson13382f42019-07-12 23:24:04 -0700224 }
225 if (manifest == null) {
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +0200226 throwToolExit('Please correct the pubspec.yaml file at $path');
Zachary Anderson13382f42019-07-12 23:24:04 -0700227 }
Mikkel Nygaard Ravna737c862018-08-10 21:49:24 +0200228 return manifest;
229 }
230
Jenn Magder5e17a242020-10-19 14:17:43 -0700231 /// Reapplies template files and regenerates project files and plugin
232 /// registrants for app and module projects only.
233 ///
234 /// Will not create project platform directories if they do not already exist.
235 Future<void> regeneratePlatformSpecificTooling() async {
236 return ensureReadyForPlatformSpecificTooling(
237 androidPlatform: android.existsSync(),
238 iosPlatform: ios.existsSync(),
239 // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
240 // desktop in existing projects are in place.
241 linuxPlatform: featureFlags.isLinuxEnabled && linux.existsSync(),
242 macOSPlatform: featureFlags.isMacOSEnabled && macos.existsSync(),
243 windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(),
244 webPlatform: featureFlags.isWebEnabled && web.existsSync(),
245 );
246 }
247
248 /// Applies template files and generates project files and plugin
249 /// registrants for app and module projects only for the specified platforms.
250 Future<void> ensureReadyForPlatformSpecificTooling({
251 bool androidPlatform = false,
252 bool iosPlatform = false,
253 bool linuxPlatform = false,
254 bool macOSPlatform = false,
255 bool windowsPlatform = false,
256 bool webPlatform = false,
257 }) async {
Jonah Williamsa476a082019-04-22 15:18:15 -0700258 if (!directory.existsSync() || hasExampleApp) {
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +0200259 return;
Jonah Williamsa476a082019-04-22 15:18:15 -0700260 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700261 await refreshPluginsList(this, iosPlatform: iosPlatform, macOSPlatform: macOSPlatform);
262 if (androidPlatform) {
Jonah Williamsa476a082019-04-22 15:18:15 -0700263 await android.ensureReadyForPlatformSpecificTooling();
264 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700265 if (iosPlatform) {
Jonah Williamsa476a082019-04-22 15:18:15 -0700266 await ios.ensureReadyForPlatformSpecificTooling();
267 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700268 if (linuxPlatform) {
stuartmorgan56d68a92019-09-26 14:01:57 -0700269 await linux.ensureReadyForPlatformSpecificTooling();
270 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700271 if (macOSPlatform) {
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700272 await macos.ensureReadyForPlatformSpecificTooling();
273 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700274 if (windowsPlatform) {
stuartmorgan56d68a92019-09-26 14:01:57 -0700275 await windows.ensureReadyForPlatformSpecificTooling();
276 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700277 if (webPlatform) {
Jonah Williams6f5f0372019-02-26 16:58:24 -0800278 await web.ensureReadyForPlatformSpecificTooling();
279 }
Jenn Magder5e17a242020-10-19 14:17:43 -0700280 await injectPlugins(
281 this,
282 androidPlatform: androidPlatform,
283 iosPlatform: iosPlatform,
284 linuxPlatform: linuxPlatform,
285 macOSPlatform: macOSPlatform,
286 windowsPlatform: windowsPlatform,
287 webPlatform: webPlatform,
288 );
Mikkel Nygaard Ravn20004352018-02-16 10:17:28 +0100289 }
Balvinder Singh Gambhir3dde3e02020-09-15 23:51:54 +0530290
291 /// Returns a json encoded string containing the [appName], [version], and [buildNumber] that is used to generate version.json
292 String getVersionInfo() {
293 final Map<String, String> versionFileJson = <String, String>{
294 'app_name': manifest.appName,
295 'version': manifest.buildName,
296 'build_number': manifest.buildNumber
297 };
298 return jsonEncode(versionFileJson);
299 }
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100300}
301
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800302/// Base class for projects per platform.
303abstract class FlutterProjectPlatform {
304
305 /// Plugin's platform config key, e.g., "macos", "ios".
306 String get pluginConfigKey;
307
308 /// Whether the platform exists in the project.
309 bool existsSync();
310}
311
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700312/// Represents an Xcode-based sub-project.
313///
314/// This defines interfaces common to iOS and macOS projects.
315abstract class XcodeBasedProject {
316 /// The parent of this project.
317 FlutterProject get parent;
318
319 /// Whether the subproject (either iOS or macOS) exists in the Flutter project.
320 bool existsSync();
321
322 /// The Xcode project (.xcodeproj directory) of the host app.
323 Directory get xcodeProject;
324
325 /// The 'project.pbxproj' file of [xcodeProject].
326 File get xcodeProjectInfoFile;
327
328 /// The Xcode workspace (.xcworkspace directory) of the host app.
329 Directory get xcodeWorkspace;
330
331 /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
332 /// the Xcode build.
333 File get generatedXcodePropertiesFile;
334
335 /// The Flutter-managed Xcode config file for [mode].
336 File xcodeConfigFor(String mode);
337
Jenn Magderbd47a312019-07-30 09:58:39 -0700338 /// The script that exports environment variables needed for Flutter tools.
339 /// Can be run first in a Xcode Script build phase to make FLUTTER_ROOT,
340 /// LOCAL_ENGINE, and other Flutter variables available to any flutter
341 /// tooling (`flutter build`, etc) to convert into flags.
342 File get generatedEnvironmentVariableExportScript;
343
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700344 /// The CocoaPods 'Podfile'.
345 File get podfile;
346
347 /// The CocoaPods 'Podfile.lock'.
348 File get podfileLock;
349
350 /// The CocoaPods 'Manifest.lock'.
351 File get podManifestLock;
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700352}
353
stuartmorgan4b120502020-07-06 12:59:16 -0700354/// Represents a CMake-based sub-project.
355///
356/// This defines interfaces common to Windows and Linux projects.
357abstract class CmakeBasedProject {
358 /// The parent of this project.
359 FlutterProject get parent;
360
361 /// Whether the subproject (either Windows or Linux) exists in the Flutter project.
362 bool existsSync();
363
364 /// The native project CMake specification.
365 File get cmakeFile;
366
J-P Nurmif2536da2020-12-22 01:04:04 +0100367 /// Contains definitions for the Flutter library and the tool.
368 File get managedCmakeFile;
369
stuartmorgan4b120502020-07-06 12:59:16 -0700370 /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
371 /// the build.
372 File get generatedCmakeConfigFile;
373
Jonah Williams08576cb2020-10-12 09:31:02 -0700374 /// Included CMake with rules and variables for plugin builds.
stuartmorgan4b120502020-07-06 12:59:16 -0700375 File get generatedPluginCmakeFile;
stuartmorgan6d1c2442020-09-09 19:08:19 -0400376
377 /// The directory to write plugin symlinks.
378 Directory get pluginSymlinkDirectory;
stuartmorgan4b120502020-07-06 12:59:16 -0700379}
380
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200381/// Represents the iOS sub-project of a Flutter project.
382///
383/// Instances will reflect the contents of the `ios/` sub-folder of
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700384/// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800385class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
KyleWong60aa49e2019-01-30 12:33:16 +0800386 IosProject.fromFlutter(this.parent);
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200387
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700388 @override
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200389 final FlutterProject parent;
390
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800391 @override
392 String get pluginConfigKey => IOSPlugin.kConfigKey;
393
Todd Volkertc8c935c2019-01-30 07:32:05 -0800394 static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200395 static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)';
Jenn Magderc8c55b42020-05-04 11:31:08 -0700396 static const String _hostAppProjectName = 'Runner';
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200397
jmagman0a586102019-08-09 17:26:51 -0700398 Directory get ephemeralDirectory => parent.directory.childDirectory('.ios');
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200399 Directory get _editableDirectory => parent.directory.childDirectory('ios');
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200400
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200401 /// This parent folder of `Runner.xcodeproj`.
402 Directory get hostAppRoot {
Zachary Andersone2340c62019-09-13 14:51:35 -0700403 if (!isModule || _editableDirectory.existsSync()) {
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200404 return _editableDirectory;
Zachary Andersone2340c62019-09-13 14:51:35 -0700405 }
jmagman0a586102019-08-09 17:26:51 -0700406 return ephemeralDirectory;
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200407 }
408
409 /// The root directory of the iOS wrapping of Flutter and plugins. This is the
410 /// parent of the `Flutter/` folder into which Flutter artifacts are written
411 /// during build.
412 ///
413 /// This is the same as [hostAppRoot] except when the project is
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700414 /// a Flutter module with an editable host app.
jmagman0a586102019-08-09 17:26:51 -0700415 Directory get _flutterLibRoot => isModule ? ephemeralDirectory : _editableDirectory;
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200416
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700417 /// True, if the parent Flutter project is a module project.
418 bool get isModule => parent.isModule;
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100419
Jonah Williams15f271e2019-04-23 09:49:49 -0700420 /// Whether the flutter application has an iOS project.
421 bool get exists => hostAppRoot.existsSync();
422
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700423 @override
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200424 File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200425
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700426 @override
Jenn Magderbd47a312019-07-30 09:58:39 -0700427 File get generatedEnvironmentVariableExportScript => _flutterLibRoot.childDirectory('Flutter').childFile('flutter_export_environment.sh');
428
429 @override
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200430 File get podfile => hostAppRoot.childFile('Podfile');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200431
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700432 @override
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200433 File get podfileLock => hostAppRoot.childFile('Podfile.lock');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200434
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700435 @override
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200436 File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200437
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800438 /// The default 'Info.plist' file of the host app. The developer can change this location in Xcode.
Jenn Magderc8c55b42020-05-04 11:31:08 -0700439 File get defaultHostInfoPlist => hostAppRoot.childDirectory(_hostAppProjectName).childFile('Info.plist');
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200440
Jenn Magder357d02c2019-10-15 15:33:55 -0700441 Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
442
443 @override
Jenn Magderc8c55b42020-05-04 11:31:08 -0700444 Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppProjectName.xcodeproj');
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200445
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700446 @override
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200447 File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
448
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700449 @override
Jenn Magderc8c55b42020-05-04 11:31:08 -0700450 Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppProjectName.xcworkspace');
Chris Brackenf8c50ea2018-09-18 10:05:46 -0700451
452 /// Xcode workspace shared data directory for the host app.
453 Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata');
454
455 /// Xcode workspace shared workspace settings file for the host app.
456 File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');
457
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700458 @override
Jonah Williams6b191842019-04-25 12:25:12 -0700459 bool existsSync() {
460 return parent.isModule || _editableDirectory.existsSync();
461 }
462
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200463 /// The product bundle identifier of the host app, or null if not set or if
464 /// iOS tooling needed to read it is not installed.
Jenn Magdere110ca72020-07-09 16:56:02 -0700465 Future<String> productBundleIdentifier(BuildInfo buildInfo) async {
466 if (!existsSync()) {
467 return null;
468 }
469 return _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
470 }
Jenn Magderc8c55b42020-05-04 11:31:08 -0700471 String _productBundleIdentifier;
472
Jenn Magder9c87b322020-05-11 11:56:44 -0700473 Future<String> _parseProductBundleIdentifier(BuildInfo buildInfo) async {
Todd Volkertc22ce952019-08-16 17:10:07 -0700474 String fromPlist;
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800475 final File defaultInfoPlist = defaultHostInfoPlist;
476 // Users can change the location of the Info.plist.
477 // Try parsing the default, first.
478 if (defaultInfoPlist.existsSync()) {
479 try {
Jonah Williams80619f12020-02-26 18:31:42 -0800480 fromPlist = globals.plistParser.getValueFromFile(
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800481 defaultHostInfoPlist.path,
482 PlistParser.kCFBundleIdentifierKey,
483 );
484 } on FileNotFoundException {
485 // iOS tooling not found; likely not running OSX; let [fromPlist] be null
486 }
Alexandre Ardhuinb953c3e2020-02-07 16:35:55 +0100487 if (fromPlist != null && !fromPlist.contains(r'$')) {
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800488 // Info.plist has no build variables in product bundle ID.
489 return fromPlist;
490 }
Todd Volkertc22ce952019-08-16 17:10:07 -0700491 }
Jenn Magder9c87b322020-05-11 11:56:44 -0700492 final Map<String, String> allBuildSettings = await buildSettingsForBuildInfo(buildInfo);
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800493 if (allBuildSettings != null) {
494 if (fromPlist != null) {
495 // Perform variable substitution using build settings.
496 return xcode.substituteXcodeVariables(fromPlist, allBuildSettings);
497 }
498 return allBuildSettings['PRODUCT_BUNDLE_IDENTIFIER'];
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200499 }
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800500
501 // On non-macOS platforms, parse the first PRODUCT_BUNDLE_IDENTIFIER from
502 // the project file. This can return the wrong bundle identifier if additional
503 // bundles have been added to the project and are found first, like frameworks
504 // or companion watchOS projects. However, on non-macOS platforms this is
505 // only used for display purposes and to regenerate organization names, so
506 // best-effort is probably fine.
Todd Volkertc8c935c2019-01-30 07:32:05 -0800507 final String fromPbxproj = _firstMatchInFile(xcodeProjectInfoFile, _productBundleIdPattern)?.group(2);
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200508 if (fromPbxproj != null && (fromPlist == null || fromPlist == _productBundleIdVariable)) {
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200509 return fromPbxproj;
510 }
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800511
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200512 return null;
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200513 }
514
Jenn Magderc8c55b42020-05-04 11:31:08 -0700515 /// The bundle name of the host app, `My App.app`.
Jenn Magdere110ca72020-07-09 16:56:02 -0700516 Future<String> hostAppBundleName(BuildInfo buildInfo) async {
517 if (!existsSync()) {
518 return null;
519 }
520 return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
521 }
Jenn Magderc8c55b42020-05-04 11:31:08 -0700522 String _hostAppBundleName;
523
Jenn Magder9c87b322020-05-11 11:56:44 -0700524 Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async {
Jenn Magderc8c55b42020-05-04 11:31:08 -0700525 // The product name and bundle name are derived from the display name, which the user
526 // is instructed to change in Xcode as part of deploying to the App Store.
527 // https://flutter.dev/docs/deployment/ios#review-xcode-project-settings
528 // The only source of truth for the name is Xcode's interpretation of the build settings.
529 String productName;
530 if (globals.xcodeProjectInterpreter.isInstalled) {
Jenn Magder9c87b322020-05-11 11:56:44 -0700531 final Map<String, String> xcodeBuildSettings = await buildSettingsForBuildInfo(buildInfo);
Jenn Magderc8c55b42020-05-04 11:31:08 -0700532 if (xcodeBuildSettings != null) {
533 productName = xcodeBuildSettings['FULL_PRODUCT_NAME'];
534 }
535 }
536 if (productName == null) {
537 globals.printTrace('FULL_PRODUCT_NAME not present, defaulting to $_hostAppProjectName');
538 }
539 return productName ?? '$_hostAppProjectName.app';
540 }
541
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200542 /// The build settings for the host app of this project, as a detached map.
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200543 ///
544 /// Returns null, if iOS tooling is unavailable.
Jenn Magder9c87b322020-05-11 11:56:44 -0700545 Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async {
Jenn Magdere110ca72020-07-09 16:56:02 -0700546 if (!existsSync()) {
547 return null;
548 }
Jenn Magder9c87b322020-05-11 11:56:44 -0700549 _buildSettingsByScheme ??= <String, Map<String, String>>{};
Jenn Magdere110ca72020-07-09 16:56:02 -0700550 final XcodeProjectInfo info = await projectInfo();
551 if (info == null) {
552 return null;
553 }
554
555 final String scheme = info.schemeFor(buildInfo);
556 if (scheme == null) {
557 info.reportFlavorNotFoundAndExit();
558 }
559
Jenn Magder9c87b322020-05-11 11:56:44 -0700560 return _buildSettingsByScheme[scheme] ??= await _xcodeProjectBuildSettings(scheme);
561 }
562 Map<String, Map<String, String>> _buildSettingsByScheme;
Jenn Magderc8c55b42020-05-04 11:31:08 -0700563
Jenn Magdere110ca72020-07-09 16:56:02 -0700564 Future<XcodeProjectInfo> projectInfo() async {
Jenn Magderfe670c02020-07-22 18:22:38 -0700565 if (!xcodeProject.existsSync() || !globals.xcodeProjectInterpreter.isInstalled) {
Jenn Magdere110ca72020-07-09 16:56:02 -0700566 return null;
567 }
568 return _projectInfo ??= await globals.xcodeProjectInterpreter.getInfo(hostAppRoot.path);
569 }
570 XcodeProjectInfo _projectInfo;
571
Jenn Magder9c87b322020-05-11 11:56:44 -0700572 Future<Map<String, String>> _xcodeProjectBuildSettings(String scheme) async {
Jenn Magderee845252020-03-18 15:55:09 -0700573 if (!globals.xcodeProjectInterpreter.isInstalled) {
Mikkel Nygaard Ravn6cc80082018-08-31 11:07:15 +0200574 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700575 }
Jenn Magderc8c55b42020-05-04 11:31:08 -0700576 final Map<String, String> buildSettings = await globals.xcodeProjectInterpreter.getBuildSettings(
Zachary Anderson8a33d242019-09-16 07:51:50 -0700577 xcodeProject.path,
Jenn Magder9c87b322020-05-11 11:56:44 -0700578 scheme: scheme,
Zachary Anderson8a33d242019-09-16 07:51:50 -0700579 );
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800580 if (buildSettings != null && buildSettings.isNotEmpty) {
581 // No timeouts, flakes, or errors.
Jenn Magdera6c3ffe2020-01-02 12:03:02 -0800582 return buildSettings;
583 }
584 return null;
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100585 }
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +0200586
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200587 Future<void> ensureReadyForPlatformSpecificTooling() async {
stuartmorgan685e9d12020-03-23 10:42:26 -0700588 await _regenerateFromTemplateIfNeeded();
Zachary Andersone2340c62019-09-13 14:51:35 -0700589 if (!_flutterLibRoot.existsSync()) {
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200590 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700591 }
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200592 await _updateGeneratedXcodeConfigIfNeeded();
593 }
594
tauu56b7c932020-04-15 21:13:06 +0200595 /// Check if one the [targets] of the project is a watchOS companion app target.
Jenn Magder9c87b322020-05-11 11:56:44 -0700596 Future<bool> containsWatchCompanion(List<String> targets, BuildInfo buildInfo) async {
597 final String bundleIdentifier = await productBundleIdentifier(buildInfo);
tauu56b7c932020-04-15 21:13:06 +0200598 // A bundle identifier is required for a companion app.
599 if (bundleIdentifier == null) {
600 return false;
601 }
602 for (final String target in targets) {
603 // Create Info.plist file of the target.
604 final File infoFile = hostAppRoot.childDirectory(target).childFile('Info.plist');
605 // The Info.plist file of a target contains the key WKCompanionAppBundleIdentifier,
606 // if it is a watchOS companion app.
607 if (infoFile.existsSync() && globals.plistParser.getValueFromFile(infoFile.path, 'WKCompanionAppBundleIdentifier') == bundleIdentifier) {
608 return true;
609 }
610 }
611 return false;
612 }
613
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200614 Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800615 if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200616 await xcode.updateGeneratedXcodeProperties(
617 project: parent,
618 buildInfo: BuildInfo.debug,
619 targetOverride: bundle.defaultMainPath,
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200620 );
621 }
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +0200622 }
623
stuartmorgan685e9d12020-03-23 10:42:26 -0700624 Future<void> _regenerateFromTemplateIfNeeded() async {
Zachary Andersone2340c62019-09-13 14:51:35 -0700625 if (!isModule) {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200626 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700627 }
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800628 final bool pubspecChanged = globals.fsUtils.isOlderThanReference(
Zachary Anderson29490882020-01-14 09:40:39 -0800629 entity: ephemeralDirectory,
630 referenceFile: parent.pubspecFile,
631 );
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800632 final bool toolingChanged = globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
Zachary Andersone2340c62019-09-13 14:51:35 -0700633 if (!pubspecChanged && !toolingChanged) {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200634 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700635 }
Jonah Williams523ac7b2019-12-09 21:18:09 -0800636
jmagman0a586102019-08-09 17:26:51 -0700637 _deleteIfExistsSync(ephemeralDirectory);
stuartmorgan685e9d12020-03-23 10:42:26 -0700638 await _overwriteFromTemplate(
Zachary Anderson29490882020-01-14 09:40:39 -0800639 globals.fs.path.join('module', 'ios', 'library'),
640 ephemeralDirectory,
641 );
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200642 // Add ephemeral host app, if a editable host app does not already exist.
643 if (!_editableDirectory.existsSync()) {
stuartmorgan685e9d12020-03-23 10:42:26 -0700644 await _overwriteFromTemplate(
Zachary Anderson29490882020-01-14 09:40:39 -0800645 globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
646 ephemeralDirectory,
647 );
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200648 if (hasPlugins(parent)) {
stuartmorgan685e9d12020-03-23 10:42:26 -0700649 await _overwriteFromTemplate(
Zachary Anderson29490882020-01-14 09:40:39 -0800650 globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
651 ephemeralDirectory,
652 );
Mikkel Nygaard Ravna600fe72018-09-25 21:21:13 +0200653 }
Jenn Magdera5ee7f72020-12-01 10:00:16 -0800654 copyEngineArtifactToProject(BuildMode.debug, EnvironmentType.physical);
Jenn Magder2a7d5772020-01-21 16:38:03 -0800655 }
656 }
657
Jenn Magdera5ee7f72020-12-01 10:00:16 -0800658 void copyEngineArtifactToProject(BuildMode mode, EnvironmentType environmentType) {
Jenn Magder9bbc2172020-11-24 10:16:09 -0800659 // Copy framework from engine cache. The actual build mode
Jenn Magder2a7d5772020-01-21 16:38:03 -0800660 // doesn't actually matter as it will be overwritten by xcode_backend.sh.
661 // However, cocoapods will run before that script and requires something
662 // to be in this location.
663 final Directory framework = globals.fs.directory(
664 globals.artifacts.getArtifactPath(
Jenn Magderd4150d32020-12-04 13:57:11 -0800665 Artifact.flutterXcframework,
Zachary Anderson29490882020-01-14 09:40:39 -0800666 platform: TargetPlatform.ios,
Jenn Magder2a7d5772020-01-21 16:38:03 -0800667 mode: mode,
Jenn Magdera5ee7f72020-12-01 10:00:16 -0800668 environmentType: environmentType,
Jonah Williams42c9c222020-11-18 17:05:33 -0800669 )
670 );
Jenn Magder2a7d5772020-01-21 16:38:03 -0800671 if (framework.existsSync()) {
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800672 globals.fsUtils.copyDirectorySync(
Jenn Magder2a7d5772020-01-21 16:38:03 -0800673 framework,
Jenn Magderd4150d32020-12-04 13:57:11 -0800674 engineCopyDirectory.childDirectory('Flutter.xcframework'),
Jenn Magder2a7d5772020-01-21 16:38:03 -0800675 );
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200676 }
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200677 }
678
stuartmorgan3ebebeb2019-05-31 13:19:44 -0700679 @override
Zachary Anderson29490882020-01-14 09:40:39 -0800680 File get generatedXcodePropertiesFile => _flutterLibRoot
681 .childDirectory('Flutter')
682 .childFile('Generated.xcconfig');
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200683
Jenn Magder91b82342020-11-03 12:48:25 -0800684 /// No longer compiled to this location.
685 ///
686 /// Used only for "flutter clean" to remove old references.
687 Directory get deprecatedCompiledDartFramework => _flutterLibRoot
Jenn Magder4fb9ce82020-02-27 12:18:06 -0800688 .childDirectory('Flutter')
689 .childDirectory('App.framework');
690
Jenn Magder2529e352020-11-17 10:09:29 -0800691 /// No longer copied to this location.
692 ///
693 /// Used only for "flutter clean" to remove old references.
694 Directory get deprecatedProjectFlutterFramework => _flutterLibRoot
695 .childDirectory('Flutter')
696 .childDirectory('Flutter.framework');
697
Jenn Magder617c57b2020-12-14 19:13:03 -0800698 /// Used only for "flutter clean" to remove old references.
699 File get flutterPodspec => _flutterLibRoot
700 .childDirectory('Flutter')
701 .childFile('Flutter.podspec');
702
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200703 Directory get pluginRegistrantHost {
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700704 return isModule
Zachary Anderson29490882020-01-14 09:40:39 -0800705 ? _flutterLibRoot
706 .childDirectory('Flutter')
707 .childDirectory('FlutterPluginRegistrant')
Jenn Magderc8c55b42020-05-04 11:31:08 -0700708 : hostAppRoot.childDirectory(_hostAppProjectName);
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200709 }
710
Dan Fieldbe2e7bb2020-11-17 22:19:50 -0800711 File get pluginRegistrantHeader {
712 final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
713 return registryDirectory.childFile('GeneratedPluginRegistrant.h');
714 }
715
716 File get pluginRegistrantImplementation {
717 final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
718 return registryDirectory.childFile('GeneratedPluginRegistrant.m');
719 }
720
Jonah Williams42c9c222020-11-18 17:05:33 -0800721 Directory get engineCopyDirectory {
722 return isModule
723 ? ephemeralDirectory.childDirectory('Flutter').childDirectory('engine')
724 : hostAppRoot.childDirectory('Flutter');
725 }
726
stuartmorgan685e9d12020-03-23 10:42:26 -0700727 Future<void> _overwriteFromTemplate(String path, Directory target) async {
Jonah Williamsee12d7c2020-08-21 11:11:04 -0700728 final Template template = await Template.fromName(
729 path,
730 fileSystem: globals.fs,
731 templateManifest: null,
732 logger: globals.logger,
733 templateRenderer: globals.templateRenderer,
Jonah Williamsee12d7c2020-08-21 11:11:04 -0700734 );
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200735 template.render(
736 target,
737 <String, dynamic>{
Chris Yang2e63b7d2020-06-23 17:38:03 -0700738 'ios': true,
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200739 'projectName': parent.manifest.appName,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100740 'iosIdentifier': parent.manifest.iosBundleIdentifier,
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200741 },
742 printStatusWhenWriting: false,
743 overwriteExisting: true,
744 );
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200745 }
Sigurd Meldgaard1e8ef602018-06-28 14:35:00 +0200746}
747
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200748/// Represents the Android sub-project of a Flutter project.
749///
750/// Instances will reflect the contents of the `android/` sub-folder of
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700751/// Flutter applications and the `.android/` sub-folder of Flutter module projects.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800752class AndroidProject extends FlutterProjectPlatform {
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200753 AndroidProject._(this.parent);
754
755 /// The parent of this project.
756 final FlutterProject parent;
757
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800758 @override
759 String get pluginConfigKey => AndroidPlugin.kConfigKey;
760
Alexandre Ardhuin94c7e822020-02-05 08:00:32 +0100761 static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s+[\'"](.*)[\'"]\\s*\$');
Alexandre Ardhuin5c28e3e2020-02-07 06:35:50 +0100762 static final RegExp _kotlinPluginPattern = RegExp('^\\s*apply plugin\\:\\s+[\'"]kotlin-android[\'"]\\s*\$');
Alexandre Ardhuin94c7e822020-02-05 08:00:32 +0100763 static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'"](.*)[\'"]\\s*\$');
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200764
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200765 /// The Gradle root directory of the Android host app. This is the directory
766 /// containing the `app/` subdirectory and the `settings.gradle` file that
767 /// includes it in the overall Gradle project.
768 Directory get hostAppGradleRoot {
Zachary Andersone2340c62019-09-13 14:51:35 -0700769 if (!isModule || _editableHostAppDirectory.existsSync()) {
matthew-carroll18d5b9d2018-09-18 17:58:20 -0700770 return _editableHostAppDirectory;
Zachary Andersone2340c62019-09-13 14:51:35 -0700771 }
jmagman0a586102019-08-09 17:26:51 -0700772 return ephemeralDirectory;
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200773 }
774
775 /// The Gradle root directory of the Android wrapping of Flutter and plugins.
776 /// This is the same as [hostAppGradleRoot] except when the project is
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700777 /// a Flutter module with an editable host app.
jmagman0a586102019-08-09 17:26:51 -0700778 Directory get _flutterLibGradleRoot => isModule ? ephemeralDirectory : _editableHostAppDirectory;
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200779
jmagman0a586102019-08-09 17:26:51 -0700780 Directory get ephemeralDirectory => parent.directory.childDirectory('.android');
matthew-carroll18d5b9d2018-09-18 17:58:20 -0700781 Directory get _editableHostAppDirectory => parent.directory.childDirectory('android');
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200782
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700783 /// True if the parent Flutter project is a module.
784 bool get isModule => parent.isModule;
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100785
Nolan Scobie43c1b342020-08-06 19:18:52 -0400786 /// True if the Flutter project is using the AndroidX support library.
Josh Burtond0e45a22019-06-01 13:33:02 +1200787 bool get usesAndroidX => parent.usesAndroidX;
788
Emmanuel Garcia3bbdf012019-05-29 20:56:28 -0700789 /// True, if the app project is using Kotlin.
790 bool get isKotlin {
791 final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
792 return _firstMatchInFile(gradleFile, _kotlinPluginPattern) != null;
793 }
794
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200795 File get appManifestFile {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200796 return isUsingGradle
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800797 ? globals.fs.file(globals.fs.path.join(hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200798 : hostAppGradleRoot.childFile('AndroidManifest.xml');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200799 }
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200800
Mikkel Nygaard Ravnb2800742018-08-02 14:12:25 +0200801 File get gradleAppOutV1File => gradleAppOutV1Directory.childFile('app-debug.apk');
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200802
803 Directory get gradleAppOutV1Directory {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800804 return globals.fs.directory(globals.fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk'));
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200805 }
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200806
Jonah Williams6b191842019-04-25 12:25:12 -0700807 /// Whether the current flutter project has an Android sub-project.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800808 @override
Jonah Williams6b191842019-04-25 12:25:12 -0700809 bool existsSync() {
810 return parent.isModule || _editableHostAppDirectory.existsSync();
811 }
812
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200813 bool get isUsingGradle {
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200814 return hostAppGradleRoot.childFile('build.gradle').existsSync();
Sigurd Meldgaard2d3a5c72018-07-20 08:00:30 +0200815 }
Alexandre Ardhuin27018352018-07-23 08:31:48 +0200816
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200817 String get applicationId {
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200818 final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200819 return _firstMatchInFile(gradleFile, _applicationIdPattern)?.group(1);
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100820 }
821
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200822 String get group {
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200823 final File gradleFile = hostAppGradleRoot.childFile('build.gradle');
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200824 return _firstMatchInFile(gradleFile, _groupPattern)?.group(1);
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100825 }
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100826
Emmanuel Garcia175b3722019-10-31 13:19:15 -0700827 /// The build directory where the Android artifacts are placed.
828 Directory get buildDirectory {
829 return parent.directory.childDirectory('build');
830 }
831
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200832 Future<void> ensureReadyForPlatformSpecificTooling() async {
xstere1ae4df2020-08-19 22:32:08 -0700833 if (getEmbeddingVersion() == AndroidEmbeddingVersion.v1) {
834 globals.printStatus(
835"""
836━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
837Warning
838──────────────────────────────────────────────────────────────────────────────
839Your Flutter application is created using an older version of the Android
840embedding. It's being deprecated in favor of Android embedding v2. Follow the
841steps at
842
843https://flutter.dev/go/android-project-migration
844
845to migrate your project.
846━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
847"""
848 );
849 }
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700850 if (isModule && _shouldRegenerateFromTemplate()) {
stuartmorgan685e9d12020-03-23 10:42:26 -0700851 await _regenerateLibrary();
matthew-carroll18d5b9d2018-09-18 17:58:20 -0700852 // Add ephemeral host app, if an editable host app does not already exist.
853 if (!_editableHostAppDirectory.existsSync()) {
stuartmorgan685e9d12020-03-23 10:42:26 -0700854 await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory);
855 await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory);
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200856 }
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200857 }
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200858 if (!hostAppGradleRoot.existsSync()) {
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200859 return;
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200860 }
861 gradle.updateLocalProperties(project: parent, requireAndroidSdk: false);
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200862 }
863
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200864 bool _shouldRegenerateFromTemplate() {
Zachary Anderson2c51efe2020-01-31 12:51:20 -0800865 return globals.fsUtils.isOlderThanReference(
Zachary Anderson29490882020-01-14 09:40:39 -0800866 entity: ephemeralDirectory,
867 referenceFile: parent.pubspecFile,
868 ) || globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200869 }
Mikkel Nygaard Ravn3c83c522018-08-07 23:43:15 +0200870
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200871 File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
872
Greg Spencer0ff9e8a2018-10-10 11:01:40 -0700873 Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200874
stuartmorgan685e9d12020-03-23 10:42:26 -0700875 Future<void> _regenerateLibrary() async {
jmagman0a586102019-08-09 17:26:51 -0700876 _deleteIfExistsSync(ephemeralDirectory);
stuartmorgan685e9d12020-03-23 10:42:26 -0700877 await _overwriteFromTemplate(globals.fs.path.join(
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700878 'module',
879 'android',
xster8bd2e652020-07-11 12:15:19 -0700880 'library_new_embedding',
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700881 ), ephemeralDirectory);
stuartmorgan685e9d12020-03-23 10:42:26 -0700882 await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
Emmanuel Garcia354f80b2019-12-11 16:36:03 -0800883 gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200884 }
885
stuartmorgan685e9d12020-03-23 10:42:26 -0700886 Future<void> _overwriteFromTemplate(String path, Directory target) async {
Jonah Williamsee12d7c2020-08-21 11:11:04 -0700887 final Template template = await Template.fromName(
888 path,
889 fileSystem: globals.fs,
890 templateManifest: null,
891 logger: globals.logger,
892 templateRenderer: globals.templateRenderer,
Jonah Williamsee12d7c2020-08-21 11:11:04 -0700893 );
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200894 template.render(
895 target,
896 <String, dynamic>{
Chris Yang2e63b7d2020-06-23 17:38:03 -0700897 'android': true,
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200898 'projectName': parent.manifest.appName,
899 'androidIdentifier': parent.manifest.androidPackage,
Josh Burtond0e45a22019-06-01 13:33:02 +1200900 'androidX': usesAndroidX,
Mikkel Nygaard Ravnd4e5e1e2018-08-16 13:21:55 +0200901 },
902 printStatusWhenWriting: false,
903 overwriteExisting: true,
904 );
905 }
xsterba26f922019-11-05 12:38:42 -0800906
907 AndroidEmbeddingVersion getEmbeddingVersion() {
xster9e0df252019-11-11 15:56:43 -0800908 if (isModule) {
909 // A module type's Android project is used in add-to-app scenarios and
910 // only supports the V2 embedding.
911 return AndroidEmbeddingVersion.v2;
912 }
xsterba26f922019-11-05 12:38:42 -0800913 if (appManifestFile == null || !appManifestFile.existsSync()) {
914 return AndroidEmbeddingVersion.v1;
915 }
Dan Field33aa4572020-07-07 16:40:33 -0700916 XmlDocument document;
xsterba26f922019-11-05 12:38:42 -0800917 try {
Dan Field33aa4572020-07-07 16:40:33 -0700918 document = XmlDocument.parse(appManifestFile.readAsStringSync());
919 } on XmlParserException {
xsterba26f922019-11-05 12:38:42 -0800920 throwToolExit('Error parsing $appManifestFile '
921 'Please ensure that the android manifest is a valid XML document and try again.');
922 } on FileSystemException {
923 throwToolExit('Error reading $appManifestFile even though it exists. '
924 'Please ensure that you have read permission to this file and try again.');
925 }
Dan Field33aa4572020-07-07 16:40:33 -0700926 for (final XmlElement metaData in document.findAllElements('meta-data')) {
xsterba26f922019-11-05 12:38:42 -0800927 final String name = metaData.getAttribute('android:name');
928 if (name == 'flutterEmbedding') {
929 final String embeddingVersionString = metaData.getAttribute('android:value');
930 if (embeddingVersionString == '1') {
931 return AndroidEmbeddingVersion.v1;
932 }
933 if (embeddingVersionString == '2') {
934 return AndroidEmbeddingVersion.v2;
935 }
936 }
937 }
938 return AndroidEmbeddingVersion.v1;
939 }
940}
941
942/// Iteration of the embedding Java API in the engine used by the Android project.
943enum AndroidEmbeddingVersion {
944 /// V1 APIs based on io.flutter.app.FlutterActivity.
945 v1,
946 /// V2 APIs based on io.flutter.embedding.android.FlutterActivity.
947 v2,
Mikkel Nygaard Ravnd89a6b52018-06-22 18:19:37 +0200948}
949
Jonah Williams6f5f0372019-02-26 16:58:24 -0800950/// Represents the web sub-project of a Flutter project.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800951class WebProject extends FlutterProjectPlatform {
Jonah Williams6f5f0372019-02-26 16:58:24 -0800952 WebProject._(this.parent);
953
954 final FlutterProject parent;
955
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800956 @override
957 String get pluginConfigKey => WebPlugin.kConfigKey;
958
Jonah Williams6b191842019-04-25 12:25:12 -0700959 /// Whether this flutter project has a web sub-project.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800960 @override
Jonah Williams6b191842019-04-25 12:25:12 -0700961 bool existsSync() {
Jonah Williamsf530b802019-06-10 15:37:23 -0700962 return parent.directory.childDirectory('web').existsSync()
963 && indexFile.existsSync();
Jonah Williams6b191842019-04-25 12:25:12 -0700964 }
Jonah Williamsa476a082019-04-22 15:18:15 -0700965
Harry Terkelsend33cf112019-09-03 10:37:34 -0700966 /// The 'lib' directory for the application.
967 Directory get libDirectory => parent.directory.childDirectory('lib');
968
Jonah Williams0980fa22019-09-26 13:24:05 -0700969 /// The directory containing additional files for the application.
970 Directory get directory => parent.directory.childDirectory('web');
971
Jonah Williamsc91b6572019-06-06 21:05:55 -0700972 /// The html file used to host the flutter web application.
Jonah Williamsf530b802019-06-10 15:37:23 -0700973 File get indexFile => parent.directory
974 .childDirectory('web')
975 .childFile('index.html');
Jonah Williamsc91b6572019-06-06 21:05:55 -0700976
Jonah Williamsbd413bf2019-06-07 13:15:38 -0700977 Future<void> ensureReadyForPlatformSpecificTooling() async {}
Jonah Williams6f5f0372019-02-26 16:58:24 -0800978}
979
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200980/// Deletes [directory] with all content.
981void _deleteIfExistsSync(Directory directory) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700982 if (directory.existsSync()) {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200983 directory.deleteSync(recursive: true);
Zachary Andersone2340c62019-09-13 14:51:35 -0700984 }
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200985}
986
987
988/// Returns the first line-based match for [regExp] in [file].
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100989///
990/// Assumes UTF8 encoding.
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200991Match _firstMatchInFile(File file, RegExp regExp) {
992 if (!file.existsSync()) {
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +0100993 return null;
994 }
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100995 for (final String line in file.readAsLinesSync()) {
Mikkel Nygaard Ravn22832d32018-08-30 16:18:44 +0200996 final Match match = regExp.firstMatch(line);
997 if (match != null) {
998 return match;
999 }
1000 }
1001 return null;
Mikkel Nygaard Ravn43532972018-01-18 09:21:24 +01001002}
Jonah Williams86c938b2019-04-17 12:16:55 -07001003
1004/// The macOS sub project.
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001005class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001006 MacOSProject._(this.parent);
Jonah Williams86c938b2019-04-17 12:16:55 -07001007
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001008 @override
1009 final FlutterProject parent;
Jonah Williams86c938b2019-04-17 12:16:55 -07001010
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001011 @override
1012 String get pluginConfigKey => MacOSPlugin.kConfigKey;
1013
Jenn Magderc8c55b42020-05-04 11:31:08 -07001014 static const String _hostAppProjectName = 'Runner';
stuartmorganef9866b2019-05-22 19:58:29 -04001015
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001016 @override
stuartmorgan932200c2019-05-30 13:43:31 -07001017 bool existsSync() => _macOSDirectory.existsSync();
Jonah Williams86c938b2019-04-17 12:16:55 -07001018
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001019 Directory get _macOSDirectory => parent.directory.childDirectory('macos');
Jonah Williams7f959d82019-04-22 13:51:00 -07001020
stuartmorgan932200c2019-05-30 13:43:31 -07001021 /// The directory in the project that is managed by Flutter. As much as
1022 /// possible, files that are edited by Flutter tooling after initial project
1023 /// creation should live here.
1024 Directory get managedDirectory => _macOSDirectory.childDirectory('Flutter');
1025
1026 /// The subdirectory of [managedDirectory] that contains files that are
1027 /// generated on the fly. All generated files that are not intended to be
1028 /// checked in should live here.
1029 Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
stuartmorganf8b07e22019-05-16 11:00:05 -04001030
Jonah Williams2ab46992019-07-31 16:19:22 -07001031 /// The xcfilelist used to track the inputs for the Flutter script phase in
1032 /// the Xcode build.
1033 File get inputFileList => ephemeralDirectory.childFile('FlutterInputs.xcfilelist');
1034
1035 /// The xcfilelist used to track the outputs for the Flutter script phase in
1036 /// the Xcode build.
1037 File get outputFileList => ephemeralDirectory.childFile('FlutterOutputs.xcfilelist');
1038
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001039 @override
stuartmorgan932200c2019-05-30 13:43:31 -07001040 File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig');
1041
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001042 @override
stuartmorgan932200c2019-05-30 13:43:31 -07001043 File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
Jonah Williams7f959d82019-04-22 13:51:00 -07001044
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001045 @override
stuartmorgan018d45d2019-08-02 16:44:04 -07001046 File get generatedEnvironmentVariableExportScript => ephemeralDirectory.childFile('flutter_export_environment.sh');
Jenn Magderbd47a312019-07-30 09:58:39 -07001047
1048 @override
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001049 File get podfile => _macOSDirectory.childFile('Podfile');
1050
1051 @override
1052 File get podfileLock => _macOSDirectory.childFile('Podfile.lock');
1053
1054 @override
1055 File get podManifestLock => _macOSDirectory.childDirectory('Pods').childFile('Manifest.lock');
1056
1057 @override
Jenn Magderc8c55b42020-05-04 11:31:08 -07001058 Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppProjectName.xcodeproj');
stuartmorganef9866b2019-05-22 19:58:29 -04001059
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001060 @override
1061 File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
1062
1063 @override
Jenn Magderc8c55b42020-05-04 11:31:08 -07001064 Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppProjectName.xcworkspace');
Jonah Williamsda92fc12019-04-17 22:40:26 -07001065
stuartmorganf8b07e22019-05-16 11:00:05 -04001066 /// The file where the Xcode build will write the name of the built app.
1067 ///
Chris Bracken4d992322019-05-24 19:12:45 -07001068 /// Ideally this will be replaced in the future with inspection of the Runner
stuartmorganf8b07e22019-05-16 11:00:05 -04001069 /// scheme's target.
stuartmorgan932200c2019-05-30 13:43:31 -07001070 File get nameFile => ephemeralDirectory.childFile('.app_filename');
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001071
1072 Future<void> ensureReadyForPlatformSpecificTooling() async {
1073 // TODO(stuartmorgan): Add create-from-template logic here.
1074 await _updateGeneratedXcodeConfigIfNeeded();
1075 }
1076
1077 Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -08001078 if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
stuartmorgan3ebebeb2019-05-31 13:19:44 -07001079 await xcode.updateGeneratedXcodeProperties(
1080 project: parent,
1081 buildInfo: BuildInfo.debug,
1082 useMacOSConfig: true,
1083 setSymroot: false,
1084 );
1085 }
1086 }
Jonah Williams86c938b2019-04-17 12:16:55 -07001087}
1088
Nolan Scobie43c1b342020-08-06 19:18:52 -04001089/// The Windows sub project.
stuartmorgan4b120502020-07-06 12:59:16 -07001090class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
1091 WindowsProject._(this.parent);
Jonah Williams86c938b2019-04-17 12:16:55 -07001092
stuartmorgan4b120502020-07-06 12:59:16 -07001093 @override
1094 final FlutterProject parent;
Jonah Williams86c938b2019-04-17 12:16:55 -07001095
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001096 @override
1097 String get pluginConfigKey => WindowsPlugin.kConfigKey;
1098
1099 @override
stuartmorgan4b120502020-07-06 12:59:16 -07001100 bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
Jonah Williams86c938b2019-04-17 12:16:55 -07001101
stuartmorgan4b120502020-07-06 12:59:16 -07001102 @override
1103 File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
1104
1105 @override
J-P Nurmif2536da2020-12-22 01:04:04 +01001106 File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
1107
1108 @override
stuartmorgan4b120502020-07-06 12:59:16 -07001109 File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
1110
1111 @override
1112 File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
1113
stuartmorgan6d1c2442020-09-09 19:08:19 -04001114 @override
1115 Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
1116
stuartmorgan4b120502020-07-06 12:59:16 -07001117 Directory get _editableDirectory => parent.directory.childDirectory('windows');
stuartmorgan6722fb42019-05-14 19:24:40 -04001118
stuartmorgane6ae95c2019-09-11 07:46:57 -07001119 /// The directory in the project that is managed by Flutter. As much as
1120 /// possible, files that are edited by Flutter tooling after initial project
1121 /// creation should live here.
1122 Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
1123
1124 /// The subdirectory of [managedDirectory] that contains files that are
1125 /// generated on the fly. All generated files that are not intended to be
1126 /// checked in should live here.
1127 Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
stuartmorgan4e1bfca2019-05-15 19:32:47 -04001128
stuartmorgan56d68a92019-09-26 14:01:57 -07001129 Future<void> ensureReadyForPlatformSpecificTooling() async {}
Jonah Williams86c938b2019-04-17 12:16:55 -07001130}
1131
1132/// The Linux sub project.
stuartmorgan4b120502020-07-06 12:59:16 -07001133class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
1134 LinuxProject._(this.parent);
Jonah Williams86c938b2019-04-17 12:16:55 -07001135
stuartmorgan4b120502020-07-06 12:59:16 -07001136 @override
1137 final FlutterProject parent;
Jonah Williams86c938b2019-04-17 12:16:55 -07001138
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001139 @override
1140 String get pluginConfigKey => LinuxPlugin.kConfigKey;
1141
Robert Ancell392e25c2020-07-30 08:46:29 +12001142 static final RegExp _applicationIdPattern = RegExp(r'''^\s*set\s*\(\s*APPLICATION_ID\s*"(.*)"\s*\)\s*$''');
1143
stuartmorgan4b120502020-07-06 12:59:16 -07001144 Directory get _editableDirectory => parent.directory.childDirectory('linux');
Jonah Williams86c938b2019-04-17 12:16:55 -07001145
stuartmorgand03aeca2019-09-16 16:04:55 -07001146 /// The directory in the project that is managed by Flutter. As much as
1147 /// possible, files that are edited by Flutter tooling after initial project
1148 /// creation should live here.
1149 Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
Jonah Williams8b0243f2019-05-11 00:08:29 -07001150
stuartmorgand03aeca2019-09-16 16:04:55 -07001151 /// The subdirectory of [managedDirectory] that contains files that are
1152 /// generated on the fly. All generated files that are not intended to be
1153 /// checked in should live here.
1154 Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
1155
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001156 @override
stuartmorgand03aeca2019-09-16 16:04:55 -07001157 bool existsSync() => _editableDirectory.existsSync();
Jonah Williamsda92fc12019-04-17 22:40:26 -07001158
stuartmorgan4b120502020-07-06 12:59:16 -07001159 @override
stuartmorgan8abf0a62020-05-16 15:07:34 -07001160 File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
stuartmorgand03aeca2019-09-16 16:04:55 -07001161
stuartmorgan4b120502020-07-06 12:59:16 -07001162 @override
J-P Nurmif2536da2020-12-22 01:04:04 +01001163 File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
1164
1165 @override
stuartmorgan8abf0a62020-05-16 15:07:34 -07001166 File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
stuartmorgan56d68a92019-09-26 14:01:57 -07001167
stuartmorgan4b120502020-07-06 12:59:16 -07001168 @override
stuartmorgan8abf0a62020-05-16 15:07:34 -07001169 File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
stuartmorgandd2756c2020-02-27 09:45:22 -08001170
stuartmorgan6d1c2442020-09-09 19:08:19 -04001171 @override
stuartmorgan7bdd4752020-02-12 16:23:27 -08001172 Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
1173
stuartmorgan56d68a92019-09-26 14:01:57 -07001174 Future<void> ensureReadyForPlatformSpecificTooling() async {}
Robert Ancell392e25c2020-07-30 08:46:29 +12001175
1176 String get applicationId {
1177 return _firstMatchInFile(cmakeFile, _applicationIdPattern)?.group(1);
1178 }
Jonah Williams6b191842019-04-25 12:25:12 -07001179}
Zachary Anderson8841afe2019-05-14 10:59:23 -07001180
Nolan Scobie43c1b342020-08-06 19:18:52 -04001181/// The Fuchsia sub project.
Zachary Anderson8841afe2019-05-14 10:59:23 -07001182class FuchsiaProject {
1183 FuchsiaProject._(this.project);
1184
1185 final FlutterProject project;
1186
1187 Directory _editableHostAppDirectory;
1188 Directory get editableHostAppDirectory =>
1189 _editableHostAppDirectory ??= project.directory.childDirectory('fuchsia');
1190
1191 bool existsSync() => editableHostAppDirectory.existsSync();
1192
1193 Directory _meta;
1194 Directory get meta =>
1195 _meta ??= editableHostAppDirectory.childDirectory('meta');
1196}