mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Split project.dart into CMake and Xcode projects (#85359)
This commit is contained in:
parent
610ee89b17
commit
e028d0f046
@ -443,3 +443,19 @@ String interpolateString(String toInterpolate, Map<String, String> replacementVa
|
||||
List<String> interpolateStringList(List<String> toInterpolate, Map<String, String> replacementValues) {
|
||||
return toInterpolate.map((String s) => interpolateString(s, replacementValues)).toList();
|
||||
}
|
||||
|
||||
/// Returns the first line-based match for [regExp] in [file].
|
||||
///
|
||||
/// Assumes UTF8 encoding.
|
||||
Match? firstMatchInFile(File file, RegExp regExp) {
|
||||
if (!file.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
for (final String line in file.readAsLinesSync()) {
|
||||
final Match? match = regExp.firstMatch(line);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'base/file_system.dart';
|
||||
import 'project.dart';
|
||||
import 'cmake_project.dart';
|
||||
|
||||
/// Extracts the `BINARY_NAME` from a project's CMake file.
|
||||
///
|
||||
|
175
packages/flutter_tools/lib/src/cmake_project.dart
Normal file
175
packages/flutter_tools/lib/src/cmake_project.dart
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import 'base/common.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'cmake.dart';
|
||||
import 'platform_plugins.dart';
|
||||
import 'project.dart';
|
||||
|
||||
/// Represents a CMake-based sub-project.
|
||||
///
|
||||
/// This defines interfaces common to Windows and Linux projects.
|
||||
abstract class CmakeBasedProject {
|
||||
/// The parent of this project.
|
||||
FlutterProject get parent;
|
||||
|
||||
/// Whether the subproject (either Windows or Linux) exists in the Flutter project.
|
||||
bool existsSync();
|
||||
|
||||
/// The native project CMake specification.
|
||||
File get cmakeFile;
|
||||
|
||||
/// Contains definitions for the Flutter library and the tool.
|
||||
File get managedCmakeFile;
|
||||
|
||||
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
|
||||
/// the build.
|
||||
File get generatedCmakeConfigFile;
|
||||
|
||||
/// Included CMake with rules and variables for plugin builds.
|
||||
File get generatedPluginCmakeFile;
|
||||
|
||||
/// The directory to write plugin symlinks.
|
||||
Directory get pluginSymlinkDirectory;
|
||||
}
|
||||
|
||||
/// The Windows sub project.
|
||||
class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
|
||||
WindowsProject.fromFlutter(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => WindowsPlugin.kConfigKey;
|
||||
|
||||
String get _childDirectory => 'windows';
|
||||
|
||||
@override
|
||||
bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
|
||||
|
||||
@override
|
||||
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
|
||||
|
||||
@override
|
||||
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
|
||||
|
||||
@override
|
||||
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
|
||||
|
||||
Directory get _editableDirectory => parent.directory.childDirectory(_childDirectory);
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {}
|
||||
}
|
||||
|
||||
/// The Windows UWP version of the Windows project.
|
||||
class WindowsUwpProject extends WindowsProject {
|
||||
WindowsUwpProject.fromFlutter(FlutterProject parent) : super.fromFlutter(parent);
|
||||
|
||||
@override
|
||||
String get _childDirectory => 'winuwp';
|
||||
|
||||
File get runnerCmakeFile => _editableDirectory.childDirectory('runner_uwp').childFile('CMakeLists.txt');
|
||||
|
||||
/// Eventually this will be used to check if the user's unstable project needs to be regenerated.
|
||||
int? get projectVersion => int.tryParse(_editableDirectory.childFile('project_version').readAsStringSync());
|
||||
|
||||
/// Retrieve the GUID of the UWP package.
|
||||
String? get packageGuid => _packageGuid ??= getCmakePackageGuid(runnerCmakeFile);
|
||||
String? _packageGuid;
|
||||
|
||||
File get appManifest => _editableDirectory.childDirectory('runner_uwp').childFile('appxmanifest.in');
|
||||
|
||||
String? get packageVersion => _packageVersion ??= parseAppVersion(this);
|
||||
String? _packageVersion;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
String? parseAppVersion(WindowsUwpProject project) {
|
||||
final File appManifestFile = project.appManifest;
|
||||
if (!appManifestFile.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(appManifestFile.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
throwToolExit('Error parsing $appManifestFile. Please ensure that the appx manifest is a valid XML document and try again.');
|
||||
}
|
||||
for (final XmlElement metaData in document.findAllElements('Identity')) {
|
||||
return metaData.getAttribute('Version');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The Linux sub project.
|
||||
class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
|
||||
LinuxProject.fromFlutter(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => LinuxPlugin.kConfigKey;
|
||||
|
||||
static final RegExp _applicationIdPattern = RegExp(r'''^\s*set\s*\(\s*APPLICATION_ID\s*"(.*)"\s*\)\s*$''');
|
||||
|
||||
Directory get _editableDirectory => parent.directory.childDirectory('linux');
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
@override
|
||||
bool existsSync() => _editableDirectory.existsSync();
|
||||
|
||||
@override
|
||||
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
|
||||
|
||||
@override
|
||||
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
|
||||
|
||||
@override
|
||||
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {}
|
||||
|
||||
String? get applicationId {
|
||||
return firstMatchInFile(cmakeFile, _applicationIdPattern)?.group(1);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import '../application_package.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_info.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../xcode_project.dart';
|
||||
import 'plist_parser.dart';
|
||||
|
||||
/// Tests whether a [Directory] is an iOS bundle directory.
|
||||
|
@ -5,7 +5,7 @@
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
/// Update the minimum iOS deployment version to the minimum allowed by Xcode without causing a warning.
|
||||
class DeploymentTargetMigration extends ProjectMigrator {
|
||||
|
@ -5,7 +5,7 @@
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
// The Runner target should inherit its build configuration from Generated.xcconfig.
|
||||
// However the top-level Runner project should not inherit any build configuration so
|
||||
|
@ -5,7 +5,7 @@
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
// Update the xcodeproj build location. Legacy build location does not work with Swift Packages.
|
||||
class ProjectBuildLocationMigration extends ProjectMigrator {
|
||||
|
@ -6,8 +6,8 @@ import '../../base/common.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../reporting/reporting.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
// Xcode 11.4 requires linked and embedded frameworks to contain all targeted architectures before build phases are run.
|
||||
// This caused issues switching between a real device and simulator due to architecture mismatch.
|
||||
|
@ -5,7 +5,7 @@
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
// Xcode legacy build system no longer supported by Xcode.
|
||||
// Set in https://github.com/flutter/flutter/pull/21901/.
|
||||
|
@ -6,8 +6,8 @@ import '../application_package.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cmake.dart';
|
||||
import '../cmake_project.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../project.dart';
|
||||
|
||||
abstract class LinuxApp extends ApplicationPackage {
|
||||
LinuxApp({required String projectBundleId}) : super(id: projectBundleId);
|
||||
|
@ -12,11 +12,11 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../cmake.dart';
|
||||
import '../cmake_project.dart';
|
||||
import '../convert.dart';
|
||||
import '../flutter_plugins.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../migrations/cmake_custom_command_migration.dart';
|
||||
import '../project.dart';
|
||||
|
||||
// Matches the following error and warning patterns:
|
||||
// - <file path>:<line>:<column>: (fatal) error: <error...>
|
||||
|
@ -13,7 +13,7 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../ios/plist_parser.dart';
|
||||
import '../project.dart';
|
||||
import '../xcode_project.dart';
|
||||
|
||||
/// Tests whether a [FileSystemEntity] is an macOS bundle directory.
|
||||
bool _isBundleDirectory(FileSystemEntity entity) =>
|
||||
|
@ -17,8 +17,8 @@ import '../base/version.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../ios/xcodeproj.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import '../xcode_project.dart';
|
||||
|
||||
const String noCocoaPodsConsequence = '''
|
||||
CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side.
|
||||
|
@ -6,8 +6,8 @@ import '../../base/common.dart';
|
||||
import '../../base/file_system.dart';
|
||||
import '../../base/logger.dart';
|
||||
import '../../base/project_migrator.dart';
|
||||
import '../../project.dart';
|
||||
import '../../reporting/reporting.dart';
|
||||
import '../../xcode_project.dart';
|
||||
|
||||
// Remove the linking and embedding logic from the Xcode project to give the tool more control over these.
|
||||
class RemoveMacOSFrameworkLinkAndEmbeddingMigration extends ProjectMigrator {
|
||||
|
@ -5,7 +5,7 @@
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/project_migrator.dart';
|
||||
import '../project.dart';
|
||||
import '../cmake_project.dart';
|
||||
|
||||
// CMake's add_custom_command() should use VERBATIM to handle escaping of spaces
|
||||
// and special characters correctly.
|
||||
|
@ -8,22 +8,23 @@ import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../src/convert.dart';
|
||||
import 'android/gradle_utils.dart' as gradle;
|
||||
import 'artifacts.dart';
|
||||
import 'base/common.dart';
|
||||
import 'base/error_handling_io.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'build_info.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'bundle.dart' as bundle;
|
||||
import 'cmake.dart';
|
||||
import 'cmake_project.dart';
|
||||
import 'features.dart';
|
||||
import 'flutter_manifest.dart';
|
||||
import 'flutter_plugins.dart';
|
||||
import 'globals_null_migrated.dart' as globals;
|
||||
import 'ios/plist_parser.dart';
|
||||
import 'ios/xcode_build_settings.dart' as xcode;
|
||||
import 'ios/xcodeproj.dart';
|
||||
import 'platform_plugins.dart';
|
||||
import 'template.dart';
|
||||
import 'xcode_project.dart';
|
||||
|
||||
export 'cmake_project.dart';
|
||||
export 'xcode_project.dart';
|
||||
|
||||
class FlutterProjectFactory {
|
||||
FlutterProjectFactory({
|
||||
@ -175,19 +176,19 @@ class FlutterProject {
|
||||
|
||||
/// The MacOS sub project of this project.
|
||||
MacOSProject? _macos;
|
||||
MacOSProject get macos => _macos ??= MacOSProject._(this);
|
||||
MacOSProject get macos => _macos ??= MacOSProject.fromFlutter(this);
|
||||
|
||||
/// The Linux sub project of this project.
|
||||
LinuxProject? _linux;
|
||||
LinuxProject get linux => _linux ??= LinuxProject._(this);
|
||||
LinuxProject get linux => _linux ??= LinuxProject.fromFlutter(this);
|
||||
|
||||
/// The Windows sub project of this project.
|
||||
WindowsProject? _windows;
|
||||
WindowsProject get windows => _windows ??= WindowsProject._(this);
|
||||
WindowsProject get windows => _windows ??= WindowsProject.fromFlutter(this);
|
||||
|
||||
/// The Windows UWP sub project of this project.
|
||||
WindowsUwpProject? _windowUwp;
|
||||
WindowsUwpProject get windowsUwp => _windowUwp ??= WindowsUwpProject._(this);
|
||||
WindowsUwpProject get windowsUwp => _windowUwp ??= WindowsUwpProject.fromFlutter(this);
|
||||
|
||||
/// The Fuchsia sub project of this project.
|
||||
FuchsiaProject? _fuchsia;
|
||||
@ -371,484 +372,6 @@ abstract class FlutterProjectPlatform {
|
||||
bool existsSync();
|
||||
}
|
||||
|
||||
/// Represents an Xcode-based sub-project.
|
||||
///
|
||||
/// This defines interfaces common to iOS and macOS projects.
|
||||
abstract class XcodeBasedProject {
|
||||
/// The parent of this project.
|
||||
FlutterProject get parent;
|
||||
|
||||
/// Whether the subproject (either iOS or macOS) exists in the Flutter project.
|
||||
bool existsSync();
|
||||
|
||||
/// The Xcode project (.xcodeproj directory) of the host app.
|
||||
Directory get xcodeProject;
|
||||
|
||||
/// The 'project.pbxproj' file of [xcodeProject].
|
||||
File get xcodeProjectInfoFile;
|
||||
|
||||
/// The Xcode workspace (.xcworkspace directory) of the host app.
|
||||
Directory get xcodeWorkspace;
|
||||
|
||||
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
|
||||
/// the Xcode build.
|
||||
File get generatedXcodePropertiesFile;
|
||||
|
||||
/// The Flutter-managed Xcode config file for [mode].
|
||||
File xcodeConfigFor(String mode);
|
||||
|
||||
/// The script that exports environment variables needed for Flutter tools.
|
||||
/// Can be run first in a Xcode Script build phase to make FLUTTER_ROOT,
|
||||
/// LOCAL_ENGINE, and other Flutter variables available to any flutter
|
||||
/// tooling (`flutter build`, etc) to convert into flags.
|
||||
File get generatedEnvironmentVariableExportScript;
|
||||
|
||||
/// The CocoaPods 'Podfile'.
|
||||
File get podfile;
|
||||
|
||||
/// The CocoaPods 'Podfile.lock'.
|
||||
File get podfileLock;
|
||||
|
||||
/// The CocoaPods 'Manifest.lock'.
|
||||
File get podManifestLock;
|
||||
}
|
||||
|
||||
/// Represents a CMake-based sub-project.
|
||||
///
|
||||
/// This defines interfaces common to Windows and Linux projects.
|
||||
abstract class CmakeBasedProject {
|
||||
/// The parent of this project.
|
||||
FlutterProject get parent;
|
||||
|
||||
/// Whether the subproject (either Windows or Linux) exists in the Flutter project.
|
||||
bool existsSync();
|
||||
|
||||
/// The native project CMake specification.
|
||||
File get cmakeFile;
|
||||
|
||||
/// Contains definitions for the Flutter library and the tool.
|
||||
File get managedCmakeFile;
|
||||
|
||||
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
|
||||
/// the build.
|
||||
File get generatedCmakeConfigFile;
|
||||
|
||||
/// Included CMake with rules and variables for plugin builds.
|
||||
File get generatedPluginCmakeFile;
|
||||
|
||||
/// The directory to write plugin symlinks.
|
||||
Directory get pluginSymlinkDirectory;
|
||||
}
|
||||
|
||||
/// Represents the iOS sub-project of a Flutter project.
|
||||
///
|
||||
/// Instances will reflect the contents of the `ios/` sub-folder of
|
||||
/// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
|
||||
class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
||||
IosProject.fromFlutter(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => IOSPlugin.kConfigKey;
|
||||
|
||||
static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
|
||||
static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)';
|
||||
static const String _hostAppProjectName = 'Runner';
|
||||
|
||||
Directory get ephemeralModuleDirectory => parent.directory.childDirectory('.ios');
|
||||
Directory get _editableDirectory => parent.directory.childDirectory('ios');
|
||||
|
||||
/// This parent folder of `Runner.xcodeproj`.
|
||||
Directory get hostAppRoot {
|
||||
if (!isModule || _editableDirectory.existsSync()) {
|
||||
return _editableDirectory;
|
||||
}
|
||||
return ephemeralModuleDirectory;
|
||||
}
|
||||
|
||||
/// The root directory of the iOS wrapping of Flutter and plugins. This is the
|
||||
/// parent of the `Flutter/` folder into which Flutter artifacts are written
|
||||
/// during build.
|
||||
///
|
||||
/// This is the same as [hostAppRoot] except when the project is
|
||||
/// a Flutter module with an editable host app.
|
||||
Directory get _flutterLibRoot => isModule ? ephemeralModuleDirectory : _editableDirectory;
|
||||
|
||||
/// True, if the parent Flutter project is a module project.
|
||||
bool get isModule => parent.isModule;
|
||||
|
||||
/// Whether the flutter application has an iOS project.
|
||||
bool get exists => hostAppRoot.existsSync();
|
||||
|
||||
/// Put generated files here.
|
||||
Directory get ephemeralDirectory => _flutterLibRoot.childDirectory('Flutter').childDirectory('ephemeral');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
|
||||
|
||||
@override
|
||||
File get generatedEnvironmentVariableExportScript => _flutterLibRoot.childDirectory('Flutter').childFile('flutter_export_environment.sh');
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => hostAppRoot.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
|
||||
|
||||
/// The default 'Info.plist' file of the host app. The developer can change this location in Xcode.
|
||||
File get defaultHostInfoPlist => hostAppRoot.childDirectory(_hostAppProjectName).childFile('Info.plist');
|
||||
|
||||
File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
||||
|
||||
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppProjectName.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
File get xcodeProjectWorkspaceData =>
|
||||
xcodeProject
|
||||
.childDirectory('project.xcworkspace')
|
||||
.childFile('contents.xcworkspacedata');
|
||||
|
||||
@override
|
||||
Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppProjectName.xcworkspace');
|
||||
|
||||
/// Xcode workspace shared data directory for the host app.
|
||||
Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata');
|
||||
|
||||
/// Xcode workspace shared workspace settings file for the host app.
|
||||
File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');
|
||||
|
||||
@override
|
||||
bool existsSync() {
|
||||
return parent.isModule || _editableDirectory.existsSync();
|
||||
}
|
||||
|
||||
/// The product bundle identifier of the host app, or null if not set or if
|
||||
/// iOS tooling needed to read it is not installed.
|
||||
Future<String?> productBundleIdentifier(BuildInfo? buildInfo) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
|
||||
}
|
||||
String? _productBundleIdentifier;
|
||||
|
||||
Future<String?> _parseProductBundleIdentifier(BuildInfo? buildInfo) async {
|
||||
String? fromPlist;
|
||||
final File defaultInfoPlist = defaultHostInfoPlist;
|
||||
// Users can change the location of the Info.plist.
|
||||
// Try parsing the default, first.
|
||||
if (defaultInfoPlist.existsSync()) {
|
||||
try {
|
||||
fromPlist = globals.plistParser.getValueFromFile(
|
||||
defaultHostInfoPlist.path,
|
||||
PlistParser.kCFBundleIdentifierKey,
|
||||
);
|
||||
} on FileNotFoundException {
|
||||
// iOS tooling not found; likely not running OSX; let [fromPlist] be null
|
||||
}
|
||||
if (fromPlist != null && !fromPlist.contains(r'$')) {
|
||||
// Info.plist has no build variables in product bundle ID.
|
||||
return fromPlist;
|
||||
}
|
||||
}
|
||||
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (allBuildSettings != null) {
|
||||
if (fromPlist != null) {
|
||||
// Perform variable substitution using build settings.
|
||||
return substituteXcodeVariables(fromPlist, allBuildSettings);
|
||||
}
|
||||
return allBuildSettings['PRODUCT_BUNDLE_IDENTIFIER'];
|
||||
}
|
||||
|
||||
// On non-macOS platforms, parse the first PRODUCT_BUNDLE_IDENTIFIER from
|
||||
// the project file. This can return the wrong bundle identifier if additional
|
||||
// bundles have been added to the project and are found first, like frameworks
|
||||
// or companion watchOS projects. However, on non-macOS platforms this is
|
||||
// only used for display purposes and to regenerate organization names, so
|
||||
// best-effort is probably fine.
|
||||
final String? fromPbxproj = _firstMatchInFile(xcodeProjectInfoFile, _productBundleIdPattern)?.group(2);
|
||||
if (fromPbxproj != null && (fromPlist == null || fromPlist == _productBundleIdVariable)) {
|
||||
return fromPbxproj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The bundle name of the host app, `My App.app`.
|
||||
Future<String?> hostAppBundleName(BuildInfo buildInfo) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
|
||||
}
|
||||
String? _hostAppBundleName;
|
||||
|
||||
Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async {
|
||||
// The product name and bundle name are derived from the display name, which the user
|
||||
// is instructed to change in Xcode as part of deploying to the App Store.
|
||||
// https://flutter.dev/docs/deployment/ios#review-xcode-project-settings
|
||||
// The only source of truth for the name is Xcode's interpretation of the build settings.
|
||||
String? productName;
|
||||
if (globals.xcodeProjectInterpreter?.isInstalled == true) {
|
||||
final Map<String, String>? xcodeBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (xcodeBuildSettings != null) {
|
||||
productName = xcodeBuildSettings['FULL_PRODUCT_NAME'];
|
||||
}
|
||||
}
|
||||
if (productName == null) {
|
||||
globals.printTrace('FULL_PRODUCT_NAME not present, defaulting to $_hostAppProjectName');
|
||||
}
|
||||
return productName ?? '$_hostAppProjectName.app';
|
||||
}
|
||||
|
||||
/// The build settings for the host app of this project, as a detached map.
|
||||
///
|
||||
/// Returns null, if iOS tooling is unavailable.
|
||||
Future<Map<String, String>?> buildSettingsForBuildInfo(BuildInfo? buildInfo, { EnvironmentType environmentType = EnvironmentType.physical }) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
final XcodeProjectInfo? info = await projectInfo();
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String? scheme = info.schemeFor(buildInfo);
|
||||
if (scheme == null) {
|
||||
info.reportFlavorNotFoundAndExit();
|
||||
}
|
||||
|
||||
final String? configuration = (await projectInfo())?.buildConfigurationFor(
|
||||
buildInfo,
|
||||
scheme,
|
||||
);
|
||||
final XcodeProjectBuildContext buildContext = XcodeProjectBuildContext(environmentType: environmentType, scheme: scheme, configuration: configuration);
|
||||
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
|
||||
if (currentBuildSettings == null) {
|
||||
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
|
||||
if (calculatedBuildSettings != null) {
|
||||
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
|
||||
}
|
||||
}
|
||||
return _buildSettingsByBuildContext[buildContext];
|
||||
}
|
||||
|
||||
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
|
||||
|
||||
Future<XcodeProjectInfo?> projectInfo() async {
|
||||
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
|
||||
if (!xcodeProject.existsSync() || xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
|
||||
return null;
|
||||
}
|
||||
return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path);
|
||||
}
|
||||
XcodeProjectInfo? _projectInfo;
|
||||
|
||||
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
|
||||
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
|
||||
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
|
||||
xcodeProject.path,
|
||||
buildContext: buildContext,
|
||||
);
|
||||
if (buildSettings != null && buildSettings.isNotEmpty) {
|
||||
// No timeouts, flakes, or errors.
|
||||
return buildSettings;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
await _regenerateFromTemplateIfNeeded();
|
||||
if (!_flutterLibRoot.existsSync()) {
|
||||
return;
|
||||
}
|
||||
await _updateGeneratedXcodeConfigIfNeeded();
|
||||
}
|
||||
|
||||
/// Check if one the [targets] of the project is a watchOS companion app target.
|
||||
Future<bool> containsWatchCompanion(List<String> targets, BuildInfo buildInfo) async {
|
||||
final String? bundleIdentifier = await productBundleIdentifier(buildInfo);
|
||||
// A bundle identifier is required for a companion app.
|
||||
if (bundleIdentifier == null) {
|
||||
return false;
|
||||
}
|
||||
for (final String target in targets) {
|
||||
// Create Info.plist file of the target.
|
||||
final File infoFile = hostAppRoot.childDirectory(target).childFile('Info.plist');
|
||||
// The Info.plist file of a target contains the key WKCompanionAppBundleIdentifier,
|
||||
// if it is a watchOS companion app.
|
||||
if (infoFile.existsSync()) {
|
||||
final String? fromPlist = globals.plistParser.getValueFromFile(infoFile.path, 'WKCompanionAppBundleIdentifier');
|
||||
if (bundleIdentifier == fromPlist) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The key WKCompanionAppBundleIdentifier might contain an xcode variable
|
||||
// that needs to be substituted before comparing it with bundle id
|
||||
if (fromPlist != null && fromPlist.contains(r'$')) {
|
||||
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (allBuildSettings != null) {
|
||||
final String substituedVariable = substituteXcodeVariables(fromPlist, allBuildSettings);
|
||||
if (substituedVariable == bundleIdentifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
|
||||
if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
|
||||
await xcode.updateGeneratedXcodeProperties(
|
||||
project: parent,
|
||||
buildInfo: BuildInfo.debug,
|
||||
targetOverride: bundle.defaultMainPath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _regenerateFromTemplateIfNeeded() async {
|
||||
if (!isModule) {
|
||||
return;
|
||||
}
|
||||
final bool pubspecChanged = globals.fsUtils.isOlderThanReference(
|
||||
entity: ephemeralModuleDirectory,
|
||||
referenceFile: parent.pubspecFile,
|
||||
);
|
||||
final bool toolingChanged = globals.cache.isOlderThanToolsStamp(ephemeralModuleDirectory);
|
||||
if (!pubspecChanged && !toolingChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
_deleteIfExistsSync(ephemeralModuleDirectory);
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'library'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
// Add ephemeral host app, if a editable host app does not already exist.
|
||||
if (!_editableDirectory.existsSync()) {
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
if (hasPlugins(parent)) {
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
}
|
||||
// Use release mode so host project can link on bitcode variant.
|
||||
_copyEngineArtifactToProject(BuildMode.release, EnvironmentType.physical);
|
||||
}
|
||||
}
|
||||
|
||||
void _copyEngineArtifactToProject(BuildMode mode, EnvironmentType environmentType) {
|
||||
// Copy framework from engine cache. The actual build mode
|
||||
// doesn't actually matter as it will be overwritten by xcode_backend.sh.
|
||||
// However, cocoapods will run before that script and requires something
|
||||
// to be in this location.
|
||||
final Directory framework = globals.fs.directory(
|
||||
globals.artifacts?.getArtifactPath(
|
||||
Artifact.flutterXcframework,
|
||||
platform: TargetPlatform.ios,
|
||||
mode: mode,
|
||||
environmentType: environmentType,
|
||||
)
|
||||
);
|
||||
if (framework.existsSync()) {
|
||||
copyDirectory(
|
||||
framework,
|
||||
engineCopyDirectory.childDirectory('Flutter.xcframework'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
File get generatedXcodePropertiesFile => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childFile('Generated.xcconfig');
|
||||
|
||||
/// No longer compiled to this location.
|
||||
///
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
Directory get deprecatedCompiledDartFramework => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('App.framework');
|
||||
|
||||
/// No longer copied to this location.
|
||||
///
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
Directory get deprecatedProjectFlutterFramework => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('Flutter.framework');
|
||||
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
File get flutterPodspec => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childFile('Flutter.podspec');
|
||||
|
||||
Directory get pluginRegistrantHost {
|
||||
return isModule
|
||||
? _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('FlutterPluginRegistrant')
|
||||
: hostAppRoot.childDirectory(_hostAppProjectName);
|
||||
}
|
||||
|
||||
File get pluginRegistrantHeader {
|
||||
final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
|
||||
return registryDirectory.childFile('GeneratedPluginRegistrant.h');
|
||||
}
|
||||
|
||||
File get pluginRegistrantImplementation {
|
||||
final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
|
||||
return registryDirectory.childFile('GeneratedPluginRegistrant.m');
|
||||
}
|
||||
|
||||
Directory get engineCopyDirectory {
|
||||
return isModule
|
||||
? ephemeralModuleDirectory.childDirectory('Flutter').childDirectory('engine')
|
||||
: hostAppRoot.childDirectory('Flutter');
|
||||
}
|
||||
|
||||
Future<void> _overwriteFromTemplate(String path, Directory target) async {
|
||||
final Template template = await Template.fromName(
|
||||
path,
|
||||
fileSystem: globals.fs,
|
||||
templateManifest: null,
|
||||
logger: globals.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
final String iosBundleIdentifier = parent.manifest.iosBundleIdentifier ?? 'com.example.${parent.manifest.appName}';
|
||||
template.render(
|
||||
target,
|
||||
<String, Object>{
|
||||
'ios': true,
|
||||
'projectName': parent.manifest.appName,
|
||||
'iosIdentifier': iosBundleIdentifier,
|
||||
},
|
||||
printStatusWhenWriting: false,
|
||||
overwriteExisting: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the Android sub-project of a Flutter project.
|
||||
///
|
||||
/// Instances will reflect the contents of the `android/` sub-folder of
|
||||
@ -918,7 +441,7 @@ class AndroidProject extends FlutterProjectPlatform {
|
||||
/// True, if the app project is using Kotlin.
|
||||
bool get isKotlin {
|
||||
final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
|
||||
return _firstMatchInFile(gradleFile, _kotlinPluginPattern) != null;
|
||||
return firstMatchInFile(gradleFile, _kotlinPluginPattern) != null;
|
||||
}
|
||||
|
||||
File get appManifestFile {
|
||||
@ -945,12 +468,12 @@ class AndroidProject extends FlutterProjectPlatform {
|
||||
|
||||
String? get applicationId {
|
||||
final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle');
|
||||
return _firstMatchInFile(gradleFile, _applicationIdPattern)?.group(1);
|
||||
return firstMatchInFile(gradleFile, _applicationIdPattern)?.group(1);
|
||||
}
|
||||
|
||||
String? get group {
|
||||
final File gradleFile = hostAppGradleRoot.childFile('build.gradle');
|
||||
return _firstMatchInFile(gradleFile, _groupPattern)?.group(1);
|
||||
return firstMatchInFile(gradleFile, _groupPattern)?.group(1);
|
||||
}
|
||||
|
||||
/// The build directory where the Android artifacts are placed.
|
||||
@ -1002,7 +525,7 @@ to migrate your project.
|
||||
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
|
||||
|
||||
Future<void> _regenerateLibrary() async {
|
||||
_deleteIfExistsSync(ephemeralDirectory);
|
||||
ErrorHandlingFileSystem.deleteIfExists(ephemeralDirectory, recursive: true);
|
||||
await _overwriteFromTemplate(globals.fs.path.join(
|
||||
'module',
|
||||
'android',
|
||||
@ -1107,249 +630,6 @@ class WebProject extends FlutterProjectPlatform {
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {}
|
||||
}
|
||||
|
||||
/// Deletes [directory] with all content.
|
||||
void _deleteIfExistsSync(Directory directory) {
|
||||
if (directory.existsSync()) {
|
||||
directory.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns the first line-based match for [regExp] in [file].
|
||||
///
|
||||
/// Assumes UTF8 encoding.
|
||||
Match? _firstMatchInFile(File file, RegExp regExp) {
|
||||
if (!file.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
for (final String line in file.readAsLinesSync()) {
|
||||
final Match? match = regExp.firstMatch(line);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The macOS sub project.
|
||||
class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
||||
MacOSProject._(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => MacOSPlugin.kConfigKey;
|
||||
|
||||
static const String _hostAppProjectName = 'Runner';
|
||||
|
||||
@override
|
||||
bool existsSync() => _macOSDirectory.existsSync();
|
||||
|
||||
Directory get _macOSDirectory => parent.directory.childDirectory('macos');
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _macOSDirectory.childDirectory('Flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
/// The xcfilelist used to track the inputs for the Flutter script phase in
|
||||
/// the Xcode build.
|
||||
File get inputFileList => ephemeralDirectory.childFile('FlutterInputs.xcfilelist');
|
||||
|
||||
/// The xcfilelist used to track the outputs for the Flutter script phase in
|
||||
/// the Xcode build.
|
||||
File get outputFileList => ephemeralDirectory.childFile('FlutterOutputs.xcfilelist');
|
||||
|
||||
@override
|
||||
File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
||||
|
||||
@override
|
||||
File get generatedEnvironmentVariableExportScript => ephemeralDirectory.childFile('flutter_export_environment.sh');
|
||||
|
||||
@override
|
||||
File get podfile => _macOSDirectory.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => _macOSDirectory.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
File get podManifestLock => _macOSDirectory.childDirectory('Pods').childFile('Manifest.lock');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppProjectName.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppProjectName.xcworkspace');
|
||||
|
||||
/// The file where the Xcode build will write the name of the built app.
|
||||
///
|
||||
/// Ideally this will be replaced in the future with inspection of the Runner
|
||||
/// scheme's target.
|
||||
File get nameFile => ephemeralDirectory.childFile('.app_filename');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
// TODO(stuartmorgan): Add create-from-template logic here.
|
||||
await _updateGeneratedXcodeConfigIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
|
||||
if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
|
||||
await xcode.updateGeneratedXcodeProperties(
|
||||
project: parent,
|
||||
buildInfo: BuildInfo.debug,
|
||||
useMacOSConfig: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Windows sub project.
|
||||
class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
|
||||
WindowsProject._(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => WindowsPlugin.kConfigKey;
|
||||
|
||||
String get _childDirectory => 'windows';
|
||||
|
||||
@override
|
||||
bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
|
||||
|
||||
@override
|
||||
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
|
||||
|
||||
@override
|
||||
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
|
||||
|
||||
@override
|
||||
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
|
||||
|
||||
Directory get _editableDirectory => parent.directory.childDirectory(_childDirectory);
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {}
|
||||
}
|
||||
|
||||
/// The Windows UWP version of the Windows project.
|
||||
class WindowsUwpProject extends WindowsProject {
|
||||
WindowsUwpProject._(FlutterProject parent) : super._(parent);
|
||||
|
||||
@override
|
||||
String get _childDirectory => 'winuwp';
|
||||
|
||||
File get runnerCmakeFile => _editableDirectory.childDirectory('runner_uwp').childFile('CMakeLists.txt');
|
||||
|
||||
/// Eventually this will be used to check if the user's unstable project needs to be regenerated.
|
||||
int? get projectVersion => int.tryParse(_editableDirectory.childFile('project_version').readAsStringSync());
|
||||
|
||||
/// Retrieve the GUID of the UWP package.
|
||||
String? get packageGuid => _packageGuid ??= getCmakePackageGuid(runnerCmakeFile);
|
||||
String? _packageGuid;
|
||||
|
||||
File get appManifest => _editableDirectory.childDirectory('runner_uwp').childFile('appxmanifest.in');
|
||||
|
||||
String? get packageVersion => _packageVersion ??= parseAppVersion(this);
|
||||
String? _packageVersion;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
String? parseAppVersion(WindowsUwpProject project) {
|
||||
final File appManifestFile = project.appManifest;
|
||||
if (!appManifestFile.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(appManifestFile.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
throwToolExit('Error parsing $appManifestFile. Please ensure that the appx manifest is a valid XML document and try again.');
|
||||
}
|
||||
for (final XmlElement metaData in document.findAllElements('Identity')) {
|
||||
return metaData.getAttribute('Version');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The Linux sub project.
|
||||
class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
|
||||
LinuxProject._(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => LinuxPlugin.kConfigKey;
|
||||
|
||||
static final RegExp _applicationIdPattern = RegExp(r'''^\s*set\s*\(\s*APPLICATION_ID\s*"(.*)"\s*\)\s*$''');
|
||||
|
||||
Directory get _editableDirectory => parent.directory.childDirectory('linux');
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _editableDirectory.childDirectory('flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
@override
|
||||
bool existsSync() => _editableDirectory.existsSync();
|
||||
|
||||
@override
|
||||
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get managedCmakeFile => managedDirectory.childFile('CMakeLists.txt');
|
||||
|
||||
@override
|
||||
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
|
||||
|
||||
@override
|
||||
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
|
||||
|
||||
@override
|
||||
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {}
|
||||
|
||||
String? get applicationId {
|
||||
return _firstMatchInFile(cmakeFile, _applicationIdPattern)?.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Fuchsia sub project.
|
||||
class FuchsiaProject {
|
||||
FuchsiaProject._(this.project);
|
||||
|
@ -11,8 +11,8 @@ import '../base/file_system.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cmake.dart';
|
||||
import '../cmake_project.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../project.dart';
|
||||
|
||||
abstract class WindowsApp extends ApplicationPackage {
|
||||
WindowsApp({@required String projectBundleId}) : super(id: projectBundleId);
|
||||
|
@ -14,11 +14,11 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../cmake.dart';
|
||||
import '../cmake_project.dart';
|
||||
import '../convert.dart';
|
||||
import '../flutter_plugins.dart';
|
||||
import '../globals_null_migrated.dart' as globals;
|
||||
import '../migrations/cmake_custom_command_migration.dart';
|
||||
import '../project.dart';
|
||||
import 'install_manifest.dart';
|
||||
import 'visual_studio.dart';
|
||||
|
||||
|
@ -12,7 +12,7 @@ import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../build_info.dart';
|
||||
import '../project.dart';
|
||||
import '../cmake_project.dart';
|
||||
|
||||
/// Generate an install manifest that is required for CMAKE on UWP projects.
|
||||
Future<void> createManifest({
|
||||
|
553
packages/flutter_tools/lib/src/xcode_project.dart
Normal file
553
packages/flutter_tools/lib/src/xcode_project.dart
Normal file
@ -0,0 +1,553 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'artifacts.dart';
|
||||
import 'base/error_handling_io.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'build_info.dart';
|
||||
import 'bundle.dart' as bundle;
|
||||
import 'flutter_plugins.dart';
|
||||
import 'globals_null_migrated.dart' as globals;
|
||||
import 'ios/plist_parser.dart';
|
||||
import 'ios/xcode_build_settings.dart' as xcode;
|
||||
import 'ios/xcodeproj.dart';
|
||||
import 'platform_plugins.dart';
|
||||
import 'project.dart';
|
||||
import 'template.dart';
|
||||
|
||||
/// Represents an Xcode-based sub-project.
|
||||
///
|
||||
/// This defines interfaces common to iOS and macOS projects.
|
||||
abstract class XcodeBasedProject {
|
||||
/// The parent of this project.
|
||||
FlutterProject get parent;
|
||||
|
||||
/// Whether the subproject (either iOS or macOS) exists in the Flutter project.
|
||||
bool existsSync();
|
||||
|
||||
/// The Xcode project (.xcodeproj directory) of the host app.
|
||||
Directory get xcodeProject;
|
||||
|
||||
/// The 'project.pbxproj' file of [xcodeProject].
|
||||
File get xcodeProjectInfoFile;
|
||||
|
||||
/// The Xcode workspace (.xcworkspace directory) of the host app.
|
||||
Directory get xcodeWorkspace;
|
||||
|
||||
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
|
||||
/// the Xcode build.
|
||||
File get generatedXcodePropertiesFile;
|
||||
|
||||
/// The Flutter-managed Xcode config file for [mode].
|
||||
File xcodeConfigFor(String mode);
|
||||
|
||||
/// The script that exports environment variables needed for Flutter tools.
|
||||
/// Can be run first in a Xcode Script build phase to make FLUTTER_ROOT,
|
||||
/// LOCAL_ENGINE, and other Flutter variables available to any flutter
|
||||
/// tooling (`flutter build`, etc) to convert into flags.
|
||||
File get generatedEnvironmentVariableExportScript;
|
||||
|
||||
/// The CocoaPods 'Podfile'.
|
||||
File get podfile;
|
||||
|
||||
/// The CocoaPods 'Podfile.lock'.
|
||||
File get podfileLock;
|
||||
|
||||
/// The CocoaPods 'Manifest.lock'.
|
||||
File get podManifestLock;
|
||||
}
|
||||
|
||||
/// Represents the iOS sub-project of a Flutter project.
|
||||
///
|
||||
/// Instances will reflect the contents of the `ios/` sub-folder of
|
||||
/// Flutter applications and the `.ios/` sub-folder of Flutter module projects.
|
||||
class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
||||
IosProject.fromFlutter(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => IOSPlugin.kConfigKey;
|
||||
|
||||
static final RegExp _productBundleIdPattern = RegExp(r'''^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(["']?)(.*?)\1;\s*$''');
|
||||
static const String _productBundleIdVariable = r'$(PRODUCT_BUNDLE_IDENTIFIER)';
|
||||
static const String _hostAppProjectName = 'Runner';
|
||||
|
||||
Directory get ephemeralModuleDirectory => parent.directory.childDirectory('.ios');
|
||||
Directory get _editableDirectory => parent.directory.childDirectory('ios');
|
||||
|
||||
/// This parent folder of `Runner.xcodeproj`.
|
||||
Directory get hostAppRoot {
|
||||
if (!isModule || _editableDirectory.existsSync()) {
|
||||
return _editableDirectory;
|
||||
}
|
||||
return ephemeralModuleDirectory;
|
||||
}
|
||||
|
||||
/// The root directory of the iOS wrapping of Flutter and plugins. This is the
|
||||
/// parent of the `Flutter/` folder into which Flutter artifacts are written
|
||||
/// during build.
|
||||
///
|
||||
/// This is the same as [hostAppRoot] except when the project is
|
||||
/// a Flutter module with an editable host app.
|
||||
Directory get _flutterLibRoot => isModule ? ephemeralModuleDirectory : _editableDirectory;
|
||||
|
||||
/// True, if the parent Flutter project is a module project.
|
||||
bool get isModule => parent.isModule;
|
||||
|
||||
/// Whether the flutter application has an iOS project.
|
||||
bool get exists => hostAppRoot.existsSync();
|
||||
|
||||
/// Put generated files here.
|
||||
Directory get ephemeralDirectory => _flutterLibRoot.childDirectory('Flutter').childDirectory('ephemeral');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
|
||||
|
||||
@override
|
||||
File get generatedEnvironmentVariableExportScript => _flutterLibRoot.childDirectory('Flutter').childFile('flutter_export_environment.sh');
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => hostAppRoot.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock');
|
||||
|
||||
/// The default 'Info.plist' file of the host app. The developer can change this location in Xcode.
|
||||
File get defaultHostInfoPlist => hostAppRoot.childDirectory(_hostAppProjectName).childFile('Info.plist');
|
||||
|
||||
File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
||||
|
||||
Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('$_hostAppProjectName.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
File get xcodeProjectWorkspaceData =>
|
||||
xcodeProject
|
||||
.childDirectory('project.xcworkspace')
|
||||
.childFile('contents.xcworkspacedata');
|
||||
|
||||
@override
|
||||
Directory get xcodeWorkspace => hostAppRoot.childDirectory('$_hostAppProjectName.xcworkspace');
|
||||
|
||||
/// Xcode workspace shared data directory for the host app.
|
||||
Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata');
|
||||
|
||||
/// Xcode workspace shared workspace settings file for the host app.
|
||||
File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');
|
||||
|
||||
@override
|
||||
bool existsSync() {
|
||||
return parent.isModule || _editableDirectory.existsSync();
|
||||
}
|
||||
|
||||
/// The product bundle identifier of the host app, or null if not set or if
|
||||
/// iOS tooling needed to read it is not installed.
|
||||
Future<String?> productBundleIdentifier(BuildInfo? buildInfo) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
|
||||
}
|
||||
String? _productBundleIdentifier;
|
||||
|
||||
Future<String?> _parseProductBundleIdentifier(BuildInfo? buildInfo) async {
|
||||
String? fromPlist;
|
||||
final File defaultInfoPlist = defaultHostInfoPlist;
|
||||
// Users can change the location of the Info.plist.
|
||||
// Try parsing the default, first.
|
||||
if (defaultInfoPlist.existsSync()) {
|
||||
try {
|
||||
fromPlist = globals.plistParser.getValueFromFile(
|
||||
defaultHostInfoPlist.path,
|
||||
PlistParser.kCFBundleIdentifierKey,
|
||||
);
|
||||
} on FileNotFoundException {
|
||||
// iOS tooling not found; likely not running OSX; let [fromPlist] be null
|
||||
}
|
||||
if (fromPlist != null && !fromPlist.contains(r'$')) {
|
||||
// Info.plist has no build variables in product bundle ID.
|
||||
return fromPlist;
|
||||
}
|
||||
}
|
||||
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (allBuildSettings != null) {
|
||||
if (fromPlist != null) {
|
||||
// Perform variable substitution using build settings.
|
||||
return substituteXcodeVariables(fromPlist, allBuildSettings);
|
||||
}
|
||||
return allBuildSettings['PRODUCT_BUNDLE_IDENTIFIER'];
|
||||
}
|
||||
|
||||
// On non-macOS platforms, parse the first PRODUCT_BUNDLE_IDENTIFIER from
|
||||
// the project file. This can return the wrong bundle identifier if additional
|
||||
// bundles have been added to the project and are found first, like frameworks
|
||||
// or companion watchOS projects. However, on non-macOS platforms this is
|
||||
// only used for display purposes and to regenerate organization names, so
|
||||
// best-effort is probably fine.
|
||||
final String? fromPbxproj = firstMatchInFile(xcodeProjectInfoFile, _productBundleIdPattern)?.group(2);
|
||||
if (fromPbxproj != null && (fromPlist == null || fromPlist == _productBundleIdVariable)) {
|
||||
return fromPbxproj;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The bundle name of the host app, `My App.app`.
|
||||
Future<String?> hostAppBundleName(BuildInfo buildInfo) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
|
||||
}
|
||||
String? _hostAppBundleName;
|
||||
|
||||
Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async {
|
||||
// The product name and bundle name are derived from the display name, which the user
|
||||
// is instructed to change in Xcode as part of deploying to the App Store.
|
||||
// https://flutter.dev/docs/deployment/ios#review-xcode-project-settings
|
||||
// The only source of truth for the name is Xcode's interpretation of the build settings.
|
||||
String? productName;
|
||||
if (globals.xcodeProjectInterpreter?.isInstalled == true) {
|
||||
final Map<String, String>? xcodeBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (xcodeBuildSettings != null) {
|
||||
productName = xcodeBuildSettings['FULL_PRODUCT_NAME'];
|
||||
}
|
||||
}
|
||||
if (productName == null) {
|
||||
globals.printTrace('FULL_PRODUCT_NAME not present, defaulting to $_hostAppProjectName');
|
||||
}
|
||||
return productName ?? '$_hostAppProjectName.app';
|
||||
}
|
||||
|
||||
/// The build settings for the host app of this project, as a detached map.
|
||||
///
|
||||
/// Returns null, if iOS tooling is unavailable.
|
||||
Future<Map<String, String>?> buildSettingsForBuildInfo(BuildInfo? buildInfo, { EnvironmentType environmentType = EnvironmentType.physical }) async {
|
||||
if (!existsSync()) {
|
||||
return null;
|
||||
}
|
||||
final XcodeProjectInfo? info = await projectInfo();
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String? scheme = info.schemeFor(buildInfo);
|
||||
if (scheme == null) {
|
||||
info.reportFlavorNotFoundAndExit();
|
||||
}
|
||||
|
||||
final String? configuration = (await projectInfo())?.buildConfigurationFor(
|
||||
buildInfo,
|
||||
scheme,
|
||||
);
|
||||
final XcodeProjectBuildContext buildContext = XcodeProjectBuildContext(environmentType: environmentType, scheme: scheme, configuration: configuration);
|
||||
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
|
||||
if (currentBuildSettings == null) {
|
||||
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
|
||||
if (calculatedBuildSettings != null) {
|
||||
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
|
||||
}
|
||||
}
|
||||
return _buildSettingsByBuildContext[buildContext];
|
||||
}
|
||||
|
||||
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
|
||||
|
||||
Future<XcodeProjectInfo?> projectInfo() async {
|
||||
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
|
||||
if (!xcodeProject.existsSync() || xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
|
||||
return null;
|
||||
}
|
||||
return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path);
|
||||
}
|
||||
XcodeProjectInfo? _projectInfo;
|
||||
|
||||
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
|
||||
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
|
||||
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
|
||||
xcodeProject.path,
|
||||
buildContext: buildContext,
|
||||
);
|
||||
if (buildSettings != null && buildSettings.isNotEmpty) {
|
||||
// No timeouts, flakes, or errors.
|
||||
return buildSettings;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
await _regenerateFromTemplateIfNeeded();
|
||||
if (!_flutterLibRoot.existsSync()) {
|
||||
return;
|
||||
}
|
||||
await _updateGeneratedXcodeConfigIfNeeded();
|
||||
}
|
||||
|
||||
/// Check if one the [targets] of the project is a watchOS companion app target.
|
||||
Future<bool> containsWatchCompanion(List<String> targets, BuildInfo buildInfo) async {
|
||||
final String? bundleIdentifier = await productBundleIdentifier(buildInfo);
|
||||
// A bundle identifier is required for a companion app.
|
||||
if (bundleIdentifier == null) {
|
||||
return false;
|
||||
}
|
||||
for (final String target in targets) {
|
||||
// Create Info.plist file of the target.
|
||||
final File infoFile = hostAppRoot.childDirectory(target).childFile('Info.plist');
|
||||
// The Info.plist file of a target contains the key WKCompanionAppBundleIdentifier,
|
||||
// if it is a watchOS companion app.
|
||||
if (infoFile.existsSync()) {
|
||||
final String? fromPlist = globals.plistParser.getValueFromFile(infoFile.path, 'WKCompanionAppBundleIdentifier');
|
||||
if (bundleIdentifier == fromPlist) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The key WKCompanionAppBundleIdentifier might contain an xcode variable
|
||||
// that needs to be substituted before comparing it with bundle id
|
||||
if (fromPlist != null && fromPlist.contains(r'$')) {
|
||||
final Map<String, String>? allBuildSettings = await buildSettingsForBuildInfo(buildInfo);
|
||||
if (allBuildSettings != null) {
|
||||
final String substituedVariable = substituteXcodeVariables(fromPlist, allBuildSettings);
|
||||
if (substituedVariable == bundleIdentifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
|
||||
if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
|
||||
await xcode.updateGeneratedXcodeProperties(
|
||||
project: parent,
|
||||
buildInfo: BuildInfo.debug,
|
||||
targetOverride: bundle.defaultMainPath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _regenerateFromTemplateIfNeeded() async {
|
||||
if (!isModule) {
|
||||
return;
|
||||
}
|
||||
final bool pubspecChanged = globals.fsUtils.isOlderThanReference(
|
||||
entity: ephemeralModuleDirectory,
|
||||
referenceFile: parent.pubspecFile,
|
||||
);
|
||||
final bool toolingChanged = globals.cache.isOlderThanToolsStamp(ephemeralModuleDirectory);
|
||||
if (!pubspecChanged && !toolingChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorHandlingFileSystem.deleteIfExists(ephemeralModuleDirectory, recursive: true);
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'library'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
// Add ephemeral host app, if a editable host app does not already exist.
|
||||
if (!_editableDirectory.existsSync()) {
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
if (hasPlugins(parent)) {
|
||||
await _overwriteFromTemplate(
|
||||
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
|
||||
ephemeralModuleDirectory,
|
||||
);
|
||||
}
|
||||
// Use release mode so host project can link on bitcode variant.
|
||||
_copyEngineArtifactToProject(BuildMode.release, EnvironmentType.physical);
|
||||
}
|
||||
}
|
||||
|
||||
void _copyEngineArtifactToProject(BuildMode mode, EnvironmentType environmentType) {
|
||||
// Copy framework from engine cache. The actual build mode
|
||||
// doesn't actually matter as it will be overwritten by xcode_backend.sh.
|
||||
// However, cocoapods will run before that script and requires something
|
||||
// to be in this location.
|
||||
final Directory framework = globals.fs.directory(
|
||||
globals.artifacts?.getArtifactPath(
|
||||
Artifact.flutterXcframework,
|
||||
platform: TargetPlatform.ios,
|
||||
mode: mode,
|
||||
environmentType: environmentType,
|
||||
)
|
||||
);
|
||||
if (framework.existsSync()) {
|
||||
copyDirectory(
|
||||
framework,
|
||||
engineCopyDirectory.childDirectory('Flutter.xcframework'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
File get generatedXcodePropertiesFile => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childFile('Generated.xcconfig');
|
||||
|
||||
/// No longer compiled to this location.
|
||||
///
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
Directory get deprecatedCompiledDartFramework => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('App.framework');
|
||||
|
||||
/// No longer copied to this location.
|
||||
///
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
Directory get deprecatedProjectFlutterFramework => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('Flutter.framework');
|
||||
|
||||
/// Used only for "flutter clean" to remove old references.
|
||||
File get flutterPodspec => _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childFile('Flutter.podspec');
|
||||
|
||||
Directory get pluginRegistrantHost {
|
||||
return isModule
|
||||
? _flutterLibRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('FlutterPluginRegistrant')
|
||||
: hostAppRoot.childDirectory(_hostAppProjectName);
|
||||
}
|
||||
|
||||
File get pluginRegistrantHeader {
|
||||
final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
|
||||
return registryDirectory.childFile('GeneratedPluginRegistrant.h');
|
||||
}
|
||||
|
||||
File get pluginRegistrantImplementation {
|
||||
final Directory registryDirectory = isModule ? pluginRegistrantHost.childDirectory('Classes') : pluginRegistrantHost;
|
||||
return registryDirectory.childFile('GeneratedPluginRegistrant.m');
|
||||
}
|
||||
|
||||
Directory get engineCopyDirectory {
|
||||
return isModule
|
||||
? ephemeralModuleDirectory.childDirectory('Flutter').childDirectory('engine')
|
||||
: hostAppRoot.childDirectory('Flutter');
|
||||
}
|
||||
|
||||
Future<void> _overwriteFromTemplate(String path, Directory target) async {
|
||||
final Template template = await Template.fromName(
|
||||
path,
|
||||
fileSystem: globals.fs,
|
||||
templateManifest: null,
|
||||
logger: globals.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
final String iosBundleIdentifier = parent.manifest.iosBundleIdentifier ?? 'com.example.${parent.manifest.appName}';
|
||||
template.render(
|
||||
target,
|
||||
<String, Object>{
|
||||
'ios': true,
|
||||
'projectName': parent.manifest.appName,
|
||||
'iosIdentifier': iosBundleIdentifier,
|
||||
},
|
||||
printStatusWhenWriting: false,
|
||||
overwriteExisting: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The macOS sub project.
|
||||
class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
|
||||
MacOSProject.fromFlutter(this.parent);
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
String get pluginConfigKey => MacOSPlugin.kConfigKey;
|
||||
|
||||
static const String _hostAppProjectName = 'Runner';
|
||||
|
||||
@override
|
||||
bool existsSync() => _macOSDirectory.existsSync();
|
||||
|
||||
Directory get _macOSDirectory => parent.directory.childDirectory('macos');
|
||||
|
||||
/// The directory in the project that is managed by Flutter. As much as
|
||||
/// possible, files that are edited by Flutter tooling after initial project
|
||||
/// creation should live here.
|
||||
Directory get managedDirectory => _macOSDirectory.childDirectory('Flutter');
|
||||
|
||||
/// The subdirectory of [managedDirectory] that contains files that are
|
||||
/// generated on the fly. All generated files that are not intended to be
|
||||
/// checked in should live here.
|
||||
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
|
||||
|
||||
/// The xcfilelist used to track the inputs for the Flutter script phase in
|
||||
/// the Xcode build.
|
||||
File get inputFileList => ephemeralDirectory.childFile('FlutterInputs.xcfilelist');
|
||||
|
||||
/// The xcfilelist used to track the outputs for the Flutter script phase in
|
||||
/// the Xcode build.
|
||||
File get outputFileList => ephemeralDirectory.childFile('FlutterOutputs.xcfilelist');
|
||||
|
||||
@override
|
||||
File get generatedXcodePropertiesFile => ephemeralDirectory.childFile('Flutter-Generated.xcconfig');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
||||
|
||||
@override
|
||||
File get generatedEnvironmentVariableExportScript => ephemeralDirectory.childFile('flutter_export_environment.sh');
|
||||
|
||||
@override
|
||||
File get podfile => _macOSDirectory.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => _macOSDirectory.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
File get podManifestLock => _macOSDirectory.childDirectory('Pods').childFile('Manifest.lock');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => _macOSDirectory.childDirectory('$_hostAppProjectName.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
Directory get xcodeWorkspace => _macOSDirectory.childDirectory('$_hostAppProjectName.xcworkspace');
|
||||
|
||||
/// The file where the Xcode build will write the name of the built app.
|
||||
///
|
||||
/// Ideally this will be replaced in the future with inspection of the Runner
|
||||
/// scheme's target.
|
||||
File get nameFile => ephemeralDirectory.childFile('.app_filename');
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
// TODO(stuartmorgan): Add create-from-template logic here.
|
||||
await _updateGeneratedXcodeConfigIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _updateGeneratedXcodeConfigIfNeeded() async {
|
||||
if (globals.cache.isOlderThanToolsStamp(generatedXcodePropertiesFile)) {
|
||||
await xcode.updateGeneratedXcodeProperties(
|
||||
project: parent,
|
||||
buildInfo: BuildInfo.debug,
|
||||
useMacOSConfig: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@ import 'package:flutter_tools/src/ios/migrations/project_base_configuration_migr
|
||||
import 'package:flutter_tools/src/ios/migrations/project_build_location_migration.dart';
|
||||
import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embedding_migration.dart';
|
||||
import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:flutter_tools/src/xcode_project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
@ -7,8 +7,8 @@ import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/project_migrator.dart';
|
||||
import 'package:flutter_tools/src/macos/migrations/remove_macos_framework_link_and_embedding_migration.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:flutter_tools/src/xcode_project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
@ -7,8 +7,8 @@ import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/project_migrator.dart';
|
||||
import 'package:flutter_tools/src/base/terminal.dart';
|
||||
import 'package:flutter_tools/src/cmake_project.dart';
|
||||
import 'package:flutter_tools/src/migrations/cmake_custom_command_migration.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
Loading…
Reference in New Issue
Block a user