mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add Swift Package Manager as new opt-in feature for iOS and macOS (#146256)
This PR adds initial support for Swift Package Manager (SPM). Users must opt in. Only compatible with Xcode 15+. Fixes https://github.com/flutter/flutter/issues/146369. ## Included Features This PR includes the following features: * Enabling SPM via config `flutter config --enable-swift-package-manager` * Disabling SPM via config (will disable for all projects) `flutter config --no-enable-swift-package-manager` * Disabling SPM via pubspec.yaml (will disable for the specific project) ``` flutter: disable-swift-package-manager: true ``` * Migrating existing apps to add SPM integration if using a Flutter plugin with a Package.swift * Generates a Swift Package (named `FlutterGeneratedPluginSwiftPackage`) that handles Flutter SPM-compatible plugin dependencies. Generated package is added to the Xcode project. * Error parsing of common errors that may occur due to using CocoaPods and Swift Package Manager together * Tool will print warnings when using all Swift Package plugins and encourage you to remove CocoaPods This PR also converts `integration_test` and `integration_test_macos` plugins to be both Swift Packages and CocoaPod Pods. ## How it Works The Flutter CLI will generate a Swift Package called `FlutterGeneratedPluginSwiftPackage`, which will have local dependencies on all Swift Package compatible Flutter plugins. The `FlutterGeneratedPluginSwiftPackage` package will be added to the Xcode project via altering of the `project.pbxproj`. In addition, a "Pre-action" script will be added via altering of the `Runner.xcscheme`. This script will invoke the flutter tool to copy the Flutter/FlutterMacOS framework to the `BUILT_PRODUCTS_DIR` directory before the build starts. This is needed because plugins need to be linked to the Flutter framework and fortunately Swift Package Manager automatically uses `BUILT_PRODUCTS_DIR` as a framework search path. CocoaPods will continue to run and be used to support non-Swift Package compatible Flutter plugins. ## Not Included Features It does not include the following (will be added in future PRs): * Create plugin template * Create app template * Add-to-App integration
This commit is contained in:
parent
f0fc419a6c
commit
6d19fa3bfa
@ -793,6 +793,9 @@ Future<void> _verifyNoMissingLicenseForExtension(
|
||||
if (contents.isEmpty) {
|
||||
continue; // let's not go down the /bin/true rabbit hole
|
||||
}
|
||||
if (path.basename(file.path) == 'Package.swift') {
|
||||
continue;
|
||||
}
|
||||
if (!contents.startsWith(RegExp(header + licensePattern))) {
|
||||
errors.add(file.path);
|
||||
}
|
||||
|
@ -144,11 +144,23 @@ BuildApp() {
|
||||
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
|
||||
flutter_args+=("-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}")
|
||||
fi
|
||||
flutter_args+=("${build_mode}_macos_bundle_flutter_assets")
|
||||
|
||||
# Run flutter assemble with the build mode specific target that was passed in.
|
||||
# If no target was passed it, default to build mode specific
|
||||
# macos_bundle_flutter_assets target.
|
||||
if [[ -n "$1" ]]; then
|
||||
flutter_args+=("${build_mode}$1")
|
||||
else
|
||||
flutter_args+=("${build_mode}_macos_bundle_flutter_assets")
|
||||
fi
|
||||
|
||||
RunCommand "${flutter_args[@]}"
|
||||
}
|
||||
|
||||
PrepareFramework() {
|
||||
BuildApp "_unpack_macos"
|
||||
}
|
||||
|
||||
# Adds the App.framework as an embedded binary, the flutter_assets as
|
||||
# resources, and the native assets.
|
||||
EmbedFrameworks() {
|
||||
@ -192,5 +204,7 @@ else
|
||||
BuildApp ;;
|
||||
"embed")
|
||||
EmbedFrameworks ;;
|
||||
"prepare")
|
||||
PrepareFramework ;;
|
||||
esac
|
||||
fi
|
||||
|
@ -302,7 +302,10 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl
|
||||
system('mkdir', '-p', symlink_plugins_dir)
|
||||
|
||||
plugins_file = File.join(application_path, '..', '.flutter-plugins-dependencies')
|
||||
plugin_pods = flutter_parse_plugins_file(plugins_file, platform)
|
||||
dependencies_hash = flutter_parse_plugins_file(plugins_file)
|
||||
plugin_pods = flutter_get_plugins_list(dependencies_hash, platform)
|
||||
swift_package_manager_enabled = flutter_get_swift_package_manager_enabled(dependencies_hash)
|
||||
|
||||
plugin_pods.each do |plugin_hash|
|
||||
plugin_name = plugin_hash['name']
|
||||
plugin_path = plugin_hash['path']
|
||||
@ -319,25 +322,43 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl
|
||||
# Keep pod path relative so it can be checked into Podfile.lock.
|
||||
relative = flutter_relative_path_from_podfile(symlink)
|
||||
|
||||
# If Swift Package Manager is enabled and the plugin has a Package.swift,
|
||||
# skip from installing as a pod.
|
||||
swift_package_exists = File.exists?(File.join(relative, platform_directory, plugin_name, "Package.swift"))
|
||||
next if swift_package_manager_enabled && swift_package_exists
|
||||
|
||||
# If a plugin is Swift Package Manager compatible but not CocoaPods compatible, skip it.
|
||||
# The tool will print an error about it.
|
||||
next if swift_package_exists && !File.exists?(File.join(relative, platform_directory, plugin_name + ".podspec"))
|
||||
|
||||
pod plugin_name, path: File.join(relative, platform_directory)
|
||||
end
|
||||
end
|
||||
|
||||
# .flutter-plugins-dependencies format documented at
|
||||
# https://flutter.dev/go/plugins-list-migration
|
||||
def flutter_parse_plugins_file(file, platform)
|
||||
def flutter_parse_plugins_file(file)
|
||||
file_path = File.expand_path(file)
|
||||
return [] unless File.exist? file_path
|
||||
|
||||
dependencies_file = File.read(file)
|
||||
dependencies_hash = JSON.parse(dependencies_file)
|
||||
JSON.parse(dependencies_file)
|
||||
end
|
||||
|
||||
# .flutter-plugins-dependencies format documented at
|
||||
# https://flutter.dev/go/plugins-list-migration
|
||||
def flutter_get_plugins_list(dependencies_hash, platform)
|
||||
# dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3
|
||||
return [] unless dependencies_hash.any?
|
||||
return [] unless dependencies_hash.has_key?('plugins')
|
||||
return [] unless dependencies_hash['plugins'].has_key?(platform)
|
||||
dependencies_hash['plugins'][platform] || []
|
||||
end
|
||||
|
||||
def flutter_get_swift_package_manager_enabled(dependencies_hash)
|
||||
return false unless dependencies_hash.any?
|
||||
return false unless dependencies_hash.has_key?('swift_package_manager_enabled')
|
||||
dependencies_hash['swift_package_manager_enabled'] == true
|
||||
end
|
||||
|
||||
def flutter_relative_path_from_podfile(path)
|
||||
# defined_in_file is set by CocoaPods and is a Pathname to the Podfile.
|
||||
project_directory_pathname = defined_in_file.dirname
|
||||
|
@ -49,6 +49,8 @@ class Context {
|
||||
switch (subCommand) {
|
||||
case 'build':
|
||||
buildApp();
|
||||
case 'prepare':
|
||||
prepare();
|
||||
case 'thin':
|
||||
// No-op, thinning is handled during the bundle asset assemble build target.
|
||||
break;
|
||||
@ -351,21 +353,67 @@ class Context {
|
||||
}
|
||||
}
|
||||
|
||||
void buildApp() {
|
||||
final bool verbose = environment['VERBOSE_SCRIPT_LOGGING'] != null && environment['VERBOSE_SCRIPT_LOGGING'] != '';
|
||||
void prepare() {
|
||||
final bool verbose = (environment['VERBOSE_SCRIPT_LOGGING'] ?? '').isNotEmpty;
|
||||
final String sourceRoot = environment['SOURCE_ROOT'] ?? '';
|
||||
String projectPath = '$sourceRoot/..';
|
||||
if (environment['FLUTTER_APPLICATION_PATH'] != null) {
|
||||
projectPath = environment['FLUTTER_APPLICATION_PATH']!;
|
||||
final String projectPath = environment['FLUTTER_APPLICATION_PATH'] ?? '$sourceRoot/..';
|
||||
|
||||
final String buildMode = parseFlutterBuildMode();
|
||||
|
||||
final List<String> flutterArgs = _generateFlutterArgsForAssemble(buildMode, verbose);
|
||||
|
||||
flutterArgs.add('${buildMode}_unpack_ios');
|
||||
|
||||
final ProcessResult result = runSync(
|
||||
'${environmentEnsure('FLUTTER_ROOT')}/bin/flutter',
|
||||
flutterArgs,
|
||||
verbose: verbose,
|
||||
allowFail: true,
|
||||
workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}"
|
||||
);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
echoError('Failed to copy Flutter framework.');
|
||||
exitApp(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void buildApp() {
|
||||
final bool verbose = (environment['VERBOSE_SCRIPT_LOGGING'] ?? '').isNotEmpty;
|
||||
final String sourceRoot = environment['SOURCE_ROOT'] ?? '';
|
||||
final String projectPath = environment['FLUTTER_APPLICATION_PATH'] ?? '$sourceRoot/..';
|
||||
|
||||
final String buildMode = parseFlutterBuildMode();
|
||||
|
||||
final List<String> flutterArgs = _generateFlutterArgsForAssemble(buildMode, verbose);
|
||||
|
||||
flutterArgs.add('${buildMode}_ios_bundle_flutter_assets');
|
||||
|
||||
final ProcessResult result = runSync(
|
||||
'${environmentEnsure('FLUTTER_ROOT')}/bin/flutter',
|
||||
flutterArgs,
|
||||
verbose: verbose,
|
||||
allowFail: true,
|
||||
workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}"
|
||||
);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
echoError('Failed to package $projectPath.');
|
||||
exitApp(-1);
|
||||
}
|
||||
|
||||
streamOutput('done');
|
||||
streamOutput(' └─Compiling, linking and signing...');
|
||||
|
||||
echo('Project $projectPath built and packaged successfully.');
|
||||
}
|
||||
|
||||
List<String> _generateFlutterArgsForAssemble(String buildMode, bool verbose) {
|
||||
String targetPath = 'lib/main.dart';
|
||||
if (environment['FLUTTER_TARGET'] != null) {
|
||||
targetPath = environment['FLUTTER_TARGET']!;
|
||||
}
|
||||
|
||||
final String buildMode = parseFlutterBuildMode();
|
||||
|
||||
// Warn the user if not archiving (ACTION=install) in release mode.
|
||||
final String? action = environment['ACTION'];
|
||||
if (action == 'install' && buildMode != 'release') {
|
||||
@ -432,24 +480,6 @@ class Context {
|
||||
flutterArgs.add('-dCodeSizeDirectory=${environment['CODE_SIZE_DIRECTORY']}');
|
||||
}
|
||||
|
||||
flutterArgs.add('${buildMode}_ios_bundle_flutter_assets');
|
||||
|
||||
final ProcessResult result = runSync(
|
||||
'${environmentEnsure('FLUTTER_ROOT')}/bin/flutter',
|
||||
flutterArgs,
|
||||
verbose: verbose,
|
||||
allowFail: true,
|
||||
workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}"
|
||||
);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
echoError('Failed to package $projectPath.');
|
||||
exitApp(-1);
|
||||
}
|
||||
|
||||
streamOutput('done');
|
||||
streamOutput(' └─Compiling, linking and signing...');
|
||||
|
||||
echo('Project $projectPath built and packaged successfully.');
|
||||
return flutterArgs;
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,9 @@ List<Target> _kDefaultTargets = <Target>[
|
||||
const DebugMacOSBundleFlutterAssets(),
|
||||
const ProfileMacOSBundleFlutterAssets(),
|
||||
const ReleaseMacOSBundleFlutterAssets(),
|
||||
const DebugUnpackMacOS(),
|
||||
const ProfileUnpackMacOS(),
|
||||
const ReleaseUnpackMacOS(),
|
||||
// Linux targets
|
||||
const DebugBundleLinuxAssets(TargetPlatform.linux_x64),
|
||||
const DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
|
||||
@ -72,6 +75,9 @@ List<Target> _kDefaultTargets = <Target>[
|
||||
const DebugIosApplicationBundle(),
|
||||
const ProfileIosApplicationBundle(),
|
||||
const ReleaseIosApplicationBundle(),
|
||||
const DebugUnpackIOS(),
|
||||
const ProfileUnpackIOS(),
|
||||
const ReleaseUnpackIOS(),
|
||||
// Windows targets
|
||||
const UnpackWindows(TargetPlatform.windows_x64),
|
||||
const UnpackWindows(TargetPlatform.windows_arm64),
|
||||
|
@ -23,6 +23,7 @@ import '../globals.dart' as globals;
|
||||
import '../ios/application_package.dart';
|
||||
import '../ios/mac.dart';
|
||||
import '../ios/plist_parser.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import 'build.dart';
|
||||
@ -686,7 +687,15 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
|
||||
xcodeBuildResult = result;
|
||||
|
||||
if (!result.success) {
|
||||
await diagnoseXcodeBuildFailure(result, globals.flutterUsage, globals.logger, globals.analytics);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
result,
|
||||
analytics: globals.analytics,
|
||||
fileSystem: globals.fs,
|
||||
flutterUsage: globals.flutterUsage,
|
||||
logger: globals.logger,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: app.project.parent,
|
||||
);
|
||||
final String presentParticiple = xcodeBuildAction == XcodeBuildAction.build ? 'building' : 'archiving';
|
||||
throwToolExit('Encountered error while $presentParticiple for $logTarget.');
|
||||
}
|
||||
|
@ -272,7 +272,12 @@ class BuildIOSFrameworkCommand extends BuildFrameworkCommand {
|
||||
buildInfo, modeDirectory, iPhoneBuildOutput, simulatorBuildOutput);
|
||||
|
||||
// Build and copy plugins.
|
||||
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
|
||||
await processPodsIfNeeded(
|
||||
project.ios,
|
||||
getIosBuildDirectory(),
|
||||
buildInfo.mode,
|
||||
forceCocoaPodsOnly: true,
|
||||
);
|
||||
if (boolArg('plugins') && hasPlugins(project)) {
|
||||
await _producePlugins(buildInfo.mode, xcodeBuildConfiguration, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory);
|
||||
}
|
||||
|
@ -94,7 +94,12 @@ class BuildMacOSFrameworkCommand extends BuildFrameworkCommand {
|
||||
await _produceAppFramework(buildInfo, modeDirectory, buildOutput);
|
||||
|
||||
// Build and copy plugins.
|
||||
await processPodsIfNeeded(project.macos, getMacOSBuildDirectory(), buildInfo.mode);
|
||||
await processPodsIfNeeded(
|
||||
project.macos,
|
||||
getMacOSBuildDirectory(),
|
||||
buildInfo.mode,
|
||||
forceCocoaPodsOnly: true,
|
||||
);
|
||||
if (boolArg('plugins') && hasPlugins(project)) {
|
||||
await _producePlugins(xcodeBuildConfiguration, buildOutput, modeDirectory);
|
||||
}
|
||||
|
@ -51,6 +51,9 @@ abstract class FeatureFlags {
|
||||
/// Whether native assets compilation and bundling is enabled.
|
||||
bool get isPreviewDeviceEnabled => true;
|
||||
|
||||
/// Whether Swift Package Manager dependency management is enabled.
|
||||
bool get isSwiftPackageManagerEnabled => false;
|
||||
|
||||
/// Whether a particular feature is enabled for the current channel.
|
||||
///
|
||||
/// Prefer using one of the specific getters above instead of this API.
|
||||
@ -70,6 +73,7 @@ const List<Feature> allFeatures = <Feature>[
|
||||
cliAnimation,
|
||||
nativeAssets,
|
||||
previewDevice,
|
||||
swiftPackageManager,
|
||||
];
|
||||
|
||||
/// All current Flutter feature flags that can be configured.
|
||||
@ -175,6 +179,16 @@ const Feature previewDevice = Feature(
|
||||
),
|
||||
);
|
||||
|
||||
/// Enable Swift Package Mangaer as a darwin dependency manager.
|
||||
const Feature swiftPackageManager = Feature(
|
||||
name: 'support for Swift Package Manager for iOS and macOS',
|
||||
configSetting: 'enable-swift-package-manager',
|
||||
environmentOverride: 'SWIFT_PACKAGE_MANAGER',
|
||||
master: FeatureChannelSetting(
|
||||
available: true,
|
||||
),
|
||||
);
|
||||
|
||||
/// A [Feature] is a process for conditionally enabling tool features.
|
||||
///
|
||||
/// All settings are optional, and if not provided will generally default to
|
||||
|
@ -58,6 +58,9 @@ class FlutterFeatureFlags implements FeatureFlags {
|
||||
@override
|
||||
bool get isPreviewDeviceEnabled => isEnabled(previewDevice);
|
||||
|
||||
@override
|
||||
bool get isSwiftPackageManagerEnabled => isEnabled(swiftPackageManager);
|
||||
|
||||
@override
|
||||
bool isEnabled(Feature feature) {
|
||||
final String currentChannel = _flutterVersion.channel;
|
||||
|
@ -136,6 +136,12 @@ class FlutterManifest {
|
||||
return _flutterDescriptor['uses-material-design'] as bool? ?? false;
|
||||
}
|
||||
|
||||
/// If true, does not use Swift Package Manager as a dependency manager.
|
||||
/// CocoaPods will be used instead.
|
||||
bool get disabledSwiftPackageManager {
|
||||
return _flutterDescriptor['disable-swift-package-manager'] as bool? ?? false;
|
||||
}
|
||||
|
||||
/// True if this Flutter module should use AndroidX dependencies.
|
||||
///
|
||||
/// If false the deprecated Android Support library will be used.
|
||||
@ -547,6 +553,10 @@ void _validateFlutter(YamlMap? yaml, List<String> errors) {
|
||||
break;
|
||||
case 'deferred-components':
|
||||
_validateDeferredComponents(kvp, errors);
|
||||
case 'disable-swift-package-manager':
|
||||
if (yamlValue is! bool) {
|
||||
errors.add('Expected "$yamlKey" to be a bool, but got $yamlValue (${yamlValue.runtimeType}).');
|
||||
}
|
||||
default:
|
||||
errors.add('Unexpected child "$yamlKey" found under "flutter".');
|
||||
break;
|
||||
|
@ -22,6 +22,8 @@ import 'dart/language_version.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'features.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'macos/darwin_dependency_management.dart';
|
||||
import 'macos/swift_package_manager.dart';
|
||||
import 'platform_plugins.dart';
|
||||
import 'plugins.dart';
|
||||
import 'project.dart';
|
||||
@ -160,7 +162,11 @@ const String _kFlutterPluginsSharedDarwinSource = 'shared_darwin_source';
|
||||
///
|
||||
///
|
||||
/// Finally, returns [true] if the plugins list has changed, otherwise returns [false].
|
||||
bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
|
||||
bool _writeFlutterPluginsList(
|
||||
FlutterProject project,
|
||||
List<Plugin> plugins, {
|
||||
bool forceCocoaPodsOnly = false,
|
||||
}) {
|
||||
final File pluginsFile = project.flutterPluginsDependenciesFile;
|
||||
if (plugins.isEmpty) {
|
||||
return ErrorHandlingFileSystem.deleteIfExists(pluginsFile);
|
||||
@ -190,6 +196,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
|
||||
result['dependencyGraph'] = _createPluginLegacyDependencyGraph(plugins);
|
||||
result['date_created'] = globals.systemClock.now().toString();
|
||||
result['version'] = globals.flutterVersion.frameworkVersion;
|
||||
result['swift_package_manager_enabled'] = !forceCocoaPodsOnly && project.usesSwiftPackageManager;
|
||||
|
||||
// Only notify if the plugins list has changed. [date_created] will always be different,
|
||||
// [version] is not relevant for this check.
|
||||
@ -1000,6 +1007,7 @@ Future<void> refreshPluginsList(
|
||||
FlutterProject project, {
|
||||
bool iosPlatform = false,
|
||||
bool macOSPlatform = false,
|
||||
bool forceCocoaPodsOnly = false,
|
||||
}) async {
|
||||
final List<Plugin> plugins = await findPlugins(project);
|
||||
// Sort the plugins by name to keep ordering stable in generated files.
|
||||
@ -1008,8 +1016,12 @@ Future<void> refreshPluginsList(
|
||||
// Write the legacy plugin files to avoid breaking existing apps.
|
||||
final bool legacyChanged = _writeFlutterPluginsListLegacy(project, plugins);
|
||||
|
||||
final bool changed = _writeFlutterPluginsList(project, plugins);
|
||||
if (changed || legacyChanged) {
|
||||
final bool changed = _writeFlutterPluginsList(
|
||||
project,
|
||||
plugins,
|
||||
forceCocoaPodsOnly: forceCocoaPodsOnly,
|
||||
);
|
||||
if (changed || legacyChanged || forceCocoaPodsOnly) {
|
||||
createPluginSymlinks(project, force: true);
|
||||
if (iosPlatform) {
|
||||
globals.cocoaPods?.invalidatePodInstallOutput(project.ios);
|
||||
@ -1069,6 +1081,7 @@ Future<void> injectPlugins(
|
||||
bool macOSPlatform = false,
|
||||
bool windowsPlatform = false,
|
||||
Iterable<String>? allowedPlugins,
|
||||
DarwinDependencyManagement? darwinDependencyManagement,
|
||||
}) async {
|
||||
final List<Plugin> plugins = await findPlugins(project);
|
||||
// Sort the plugins by name to keep ordering stable in generated files.
|
||||
@ -1088,20 +1101,27 @@ Future<void> injectPlugins(
|
||||
if (windowsPlatform) {
|
||||
await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins);
|
||||
}
|
||||
if (!project.isModule) {
|
||||
final List<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[
|
||||
if (iosPlatform) project.ios,
|
||||
if (macOSPlatform) project.macos,
|
||||
];
|
||||
for (final XcodeBasedProject subproject in darwinProjects) {
|
||||
if (plugins.isNotEmpty) {
|
||||
await globals.cocoaPods?.setupPodfile(subproject);
|
||||
}
|
||||
/// The user may have a custom maintained Podfile that they're running `pod install`
|
||||
/// on themselves.
|
||||
else if (subproject.podfile.existsSync() && subproject.podfileLock.existsSync()) {
|
||||
globals.cocoaPods?.addPodsDependencyToFlutterXcconfig(subproject);
|
||||
}
|
||||
if (iosPlatform || macOSPlatform) {
|
||||
final DarwinDependencyManagement darwinDependencyManagerSetup = darwinDependencyManagement ?? DarwinDependencyManagement(
|
||||
project: project,
|
||||
plugins: plugins,
|
||||
cocoapods: globals.cocoaPods!,
|
||||
swiftPackageManager: SwiftPackageManager(
|
||||
fileSystem: globals.fs,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
),
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
);
|
||||
if (iosPlatform) {
|
||||
await darwinDependencyManagerSetup.setUp(
|
||||
platform: SupportedPlatform.ios,
|
||||
);
|
||||
}
|
||||
if (macOSPlatform) {
|
||||
await darwinDependencyManagerSetup.setUp(
|
||||
platform: SupportedPlatform.macos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -496,7 +496,15 @@ class IOSDevice extends Device {
|
||||
);
|
||||
if (!buildResult.success) {
|
||||
_logger.printError('Could not build the precompiled application for the device.');
|
||||
await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, _logger, globals.analytics);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
analytics: globals.analytics,
|
||||
fileSystem: globals.fs,
|
||||
flutterUsage: globals.flutterUsage,
|
||||
logger: globals.logger,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: package.project.parent,
|
||||
);
|
||||
_logger.printError('');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
@ -19,12 +19,16 @@ import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../device.dart';
|
||||
import '../flutter_manifest.dart';
|
||||
import '../flutter_plugins.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../macos/cocoapod_utils.dart';
|
||||
import '../macos/swift_package_manager.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../migrations/swift_package_manager_integration_migration.dart';
|
||||
import '../migrations/xcode_project_object_version_migration.dart';
|
||||
import '../migrations/xcode_script_build_phase_migration.dart';
|
||||
import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import 'application_package.dart';
|
||||
@ -148,6 +152,8 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
return XcodeBuildResult(success: false);
|
||||
}
|
||||
|
||||
final FlutterProject project = FlutterProject.current();
|
||||
|
||||
final List<ProjectMigrator> migrators = <ProjectMigrator>[
|
||||
RemoveFrameworkLinkAndEmbeddingMigration(app.project, globals.logger, globals.flutterUsage, globals.analytics),
|
||||
XcodeBuildSystemMigration(app.project, globals.logger),
|
||||
@ -160,6 +166,16 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
RemoveBitcodeMigration(app.project, globals.logger),
|
||||
XcodeThinBinaryBuildPhaseInputPathsMigration(app.project, globals.logger),
|
||||
UIApplicationMainDeprecationMigration(app.project, globals.logger),
|
||||
if (project.usesSwiftPackageManager && app.project.flutterPluginSwiftPackageManifest.existsSync())
|
||||
SwiftPackageManagerIntegrationMigration(
|
||||
app.project,
|
||||
SupportedPlatform.ios,
|
||||
buildInfo,
|
||||
xcodeProjectInterpreter: globals.xcodeProjectInterpreter!,
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
plistParser: globals.plistParser,
|
||||
),
|
||||
];
|
||||
|
||||
final ProjectMigration migration = ProjectMigration(migrators);
|
||||
@ -245,12 +261,21 @@ Future<XcodeBuildResult> buildXcodeProject({
|
||||
);
|
||||
}
|
||||
|
||||
final FlutterProject project = FlutterProject.current();
|
||||
await updateGeneratedXcodeProperties(
|
||||
project: project,
|
||||
targetOverride: targetOverride,
|
||||
buildInfo: buildInfo,
|
||||
);
|
||||
if (project.usesSwiftPackageManager) {
|
||||
final String? iosDeploymentTarget = buildSettings['IPHONEOS_DEPLOYMENT_TARGET'];
|
||||
if (iosDeploymentTarget != null) {
|
||||
SwiftPackageManager.updateMinimumDeployment(
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project.ios,
|
||||
deploymentTarget: iosDeploymentTarget,
|
||||
);
|
||||
}
|
||||
}
|
||||
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
|
||||
if (configOnly) {
|
||||
return XcodeBuildResult(success: true);
|
||||
@ -596,11 +621,14 @@ return result.exitCode != 0 &&
|
||||
}
|
||||
|
||||
Future<void> diagnoseXcodeBuildFailure(
|
||||
XcodeBuildResult result,
|
||||
Usage flutterUsage,
|
||||
Logger logger,
|
||||
Analytics analytics,
|
||||
) async {
|
||||
XcodeBuildResult result, {
|
||||
required Analytics analytics,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
required Usage flutterUsage,
|
||||
required SupportedPlatform platform,
|
||||
required FlutterProject project,
|
||||
}) async {
|
||||
final XcodeBuildExecution? xcodeBuildExecution = result.xcodeBuildExecution;
|
||||
if (xcodeBuildExecution != null
|
||||
&& xcodeBuildExecution.environmentType == EnvironmentType.physical
|
||||
@ -627,7 +655,14 @@ Future<void> diagnoseXcodeBuildFailure(
|
||||
}
|
||||
|
||||
// Handle errors.
|
||||
final bool issueDetected = _handleIssues(result.xcResult, logger, xcodeBuildExecution);
|
||||
final bool issueDetected = await _handleIssues(
|
||||
result,
|
||||
xcodeBuildExecution,
|
||||
project: project,
|
||||
platform: platform,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
);
|
||||
|
||||
if (!issueDetected && xcodeBuildExecution != null) {
|
||||
// Fallback to use stdout to detect and print issues.
|
||||
@ -728,7 +763,11 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue, required Logger logger}) {
|
||||
_XCResultIssueHandlingResult _handleXCResultIssue({
|
||||
required XCResultIssue issue,
|
||||
required XcodeBuildResult result,
|
||||
required Logger logger,
|
||||
}) {
|
||||
// Issue summary from xcresult.
|
||||
final StringBuffer issueSummaryBuffer = StringBuffer();
|
||||
issueSummaryBuffer.write(issue.subType ?? 'Unknown');
|
||||
@ -761,20 +800,61 @@ _XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue,
|
||||
if (missingPlatform != null) {
|
||||
return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false, missingPlatform: missingPlatform);
|
||||
}
|
||||
} else if (message.toLowerCase().contains('redefinition of module')) {
|
||||
final String? duplicateModule = _parseModuleRedefinition(message);
|
||||
return _XCResultIssueHandlingResult(
|
||||
requiresProvisioningProfile: false,
|
||||
hasProvisioningProfileIssue: false,
|
||||
duplicateModule: duplicateModule,
|
||||
);
|
||||
} else if (message.toLowerCase().contains('duplicate symbols')) {
|
||||
// The message does not contain the plugin name, must parse the stdout.
|
||||
String? duplicateModule;
|
||||
if (result.stdout != null) {
|
||||
duplicateModule = _parseDuplicateSymbols(result.stdout!);
|
||||
}
|
||||
return _XCResultIssueHandlingResult(
|
||||
requiresProvisioningProfile: false,
|
||||
hasProvisioningProfileIssue: false,
|
||||
duplicateModule: duplicateModule,
|
||||
);
|
||||
} else if (message.toLowerCase().contains('not found')) {
|
||||
final String? missingModule = _parseMissingModule(message);
|
||||
if (missingModule != null) {
|
||||
return _XCResultIssueHandlingResult(
|
||||
requiresProvisioningProfile: false,
|
||||
hasProvisioningProfileIssue: false,
|
||||
missingModule: missingModule,
|
||||
);
|
||||
}
|
||||
}
|
||||
return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false);
|
||||
}
|
||||
|
||||
// Returns `true` if at least one issue is detected.
|
||||
bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcodeBuildExecution) {
|
||||
Future<bool> _handleIssues(
|
||||
XcodeBuildResult result,
|
||||
XcodeBuildExecution? xcodeBuildExecution, {
|
||||
required FlutterProject project,
|
||||
required SupportedPlatform platform,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
}) async {
|
||||
bool requiresProvisioningProfile = false;
|
||||
bool hasProvisioningProfileIssue = false;
|
||||
bool issueDetected = false;
|
||||
String? missingPlatform;
|
||||
final List<String> duplicateModules = <String>[];
|
||||
final List<String> missingModules = <String>[];
|
||||
|
||||
final XCResult? xcResult = result.xcResult;
|
||||
if (xcResult != null && xcResult.parseSuccess) {
|
||||
for (final XCResultIssue issue in xcResult.issues) {
|
||||
final _XCResultIssueHandlingResult handlingResult = _handleXCResultIssue(issue: issue, logger: logger);
|
||||
final _XCResultIssueHandlingResult handlingResult = _handleXCResultIssue(
|
||||
issue: issue,
|
||||
result: result,
|
||||
logger: logger,
|
||||
);
|
||||
if (handlingResult.hasProvisioningProfileIssue) {
|
||||
hasProvisioningProfileIssue = true;
|
||||
}
|
||||
@ -782,12 +862,20 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
|
||||
requiresProvisioningProfile = true;
|
||||
}
|
||||
missingPlatform = handlingResult.missingPlatform;
|
||||
if (handlingResult.duplicateModule != null) {
|
||||
duplicateModules.add(handlingResult.duplicateModule!);
|
||||
}
|
||||
if (handlingResult.missingModule != null) {
|
||||
missingModules.add(handlingResult.missingModule!);
|
||||
}
|
||||
issueDetected = true;
|
||||
}
|
||||
} else if (xcResult != null) {
|
||||
globals.printTrace('XCResult parsing error: ${xcResult.parsingErrorMessage}');
|
||||
}
|
||||
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos;
|
||||
|
||||
if (requiresProvisioningProfile) {
|
||||
logger.printError(noProvisioningProfileInstruction, emphasis: true);
|
||||
} else if ((!issueDetected || hasProvisioningProfileIssue) && _missingDevelopmentTeam(xcodeBuildExecution)) {
|
||||
@ -803,11 +891,84 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
|
||||
logger.printError("Also try selecting 'Product > Build' to fix the problem.");
|
||||
} else if (missingPlatform != null) {
|
||||
logger.printError(missingPlatformInstructions(missingPlatform), emphasis: true);
|
||||
} else if (duplicateModules.isNotEmpty) {
|
||||
final bool usesCocoapods = xcodeProject.podfile.existsSync();
|
||||
final bool usesSwiftPackageManager = project.usesSwiftPackageManager;
|
||||
if (usesCocoapods && usesSwiftPackageManager) {
|
||||
logger.printError(
|
||||
'Your project uses both CocoaPods and Swift Package Manager, which can '
|
||||
'cause the above error. It may be caused by there being both a CocoaPod '
|
||||
'and Swift Package Manager dependency for the following module(s): '
|
||||
'${duplicateModules.join(', ')}.\n\n'
|
||||
'You can try to identify which Pod the conflicting module is from by '
|
||||
'looking at your "ios/Podfile.lock" dependency tree and requesting the '
|
||||
'author add Swift Package Manager compatibility. See https://stackoverflow.com/a/27955017 '
|
||||
'to learn more about understanding Podlock dependency tree. \n\n'
|
||||
'You can also disable Swift Package Manager for the project by adding the '
|
||||
'following in the project\'s pubspec.yaml under the "flutter" section:\n'
|
||||
' "disable-swift-package-manager: true"\n',
|
||||
);
|
||||
}
|
||||
} else if (missingModules.isNotEmpty) {
|
||||
final bool usesCocoapods = xcodeProject.podfile.existsSync();
|
||||
final bool usesSwiftPackageManager = project.usesSwiftPackageManager;
|
||||
if (usesCocoapods && !usesSwiftPackageManager) {
|
||||
final List<String> swiftPackageOnlyPlugins = <String>[];
|
||||
for (final String module in missingModules) {
|
||||
if (await _isPluginSwiftPackageOnly(
|
||||
platform: platform,
|
||||
project: project,
|
||||
pluginName: module,
|
||||
fileSystem: fileSystem,
|
||||
)) {
|
||||
swiftPackageOnlyPlugins.add(module);
|
||||
}
|
||||
}
|
||||
if (swiftPackageOnlyPlugins.isNotEmpty) {
|
||||
logger.printError(
|
||||
'Your project uses CocoaPods as a dependency manager, but the following '
|
||||
'plugin(s) only support Swift Package Manager: ${swiftPackageOnlyPlugins.join(', ')}.\n'
|
||||
'Try enabling Swift Package Manager with "flutter config --enable-swift-package-manager".',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return issueDetected;
|
||||
}
|
||||
|
||||
/// Returns true if a Package.swift is found for the plugin and a podspec is not.
|
||||
Future<bool> _isPluginSwiftPackageOnly({
|
||||
required SupportedPlatform platform,
|
||||
required FlutterProject project,
|
||||
required String pluginName,
|
||||
required FileSystem fileSystem,
|
||||
}) async {
|
||||
final List<Plugin> plugins = await findPlugins(project);
|
||||
final Plugin? matched = plugins
|
||||
.where((Plugin plugin) =>
|
||||
plugin.name.toLowerCase() == pluginName.toLowerCase() &&
|
||||
plugin.platforms[platform.name] != null)
|
||||
.firstOrNull;
|
||||
if (matched == null) {
|
||||
return false;
|
||||
}
|
||||
final String? swiftPackagePath = matched.pluginSwiftPackageManifestPath(
|
||||
fileSystem,
|
||||
platform.name,
|
||||
);
|
||||
final bool swiftPackageExists = swiftPackagePath != null &&
|
||||
fileSystem.file(swiftPackagePath).existsSync();
|
||||
|
||||
final String? podspecPath = matched.pluginPodspecPath(
|
||||
fileSystem,
|
||||
platform.name,
|
||||
);
|
||||
final bool podspecExists = podspecPath != null &&
|
||||
fileSystem.file(podspecPath).existsSync();
|
||||
|
||||
return swiftPackageExists && !podspecExists;
|
||||
}
|
||||
|
||||
// Return 'true' a missing development team issue is detected.
|
||||
bool _missingDevelopmentTeam(XcodeBuildExecution? xcodeBuildExecution) {
|
||||
// Make sure the user has specified one of:
|
||||
@ -852,22 +1013,68 @@ String? _parseMissingPlatform(String message) {
|
||||
return pattern.firstMatch(message)?.group(1);
|
||||
}
|
||||
|
||||
String? _parseModuleRedefinition(String message) {
|
||||
// Example: "Redefinition of module 'plugin_1_name'"
|
||||
final RegExp pattern = RegExp(r"Redefinition of module '(.*?)'");
|
||||
final RegExpMatch? match = pattern.firstMatch(message);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
final String? version = match.group(1);
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _parseDuplicateSymbols(String message) {
|
||||
// Example: "duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in:
|
||||
// /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o)
|
||||
final RegExp pattern = RegExp(r'duplicate symbol [\s|\S]*?\/(.*)\.o');
|
||||
final RegExpMatch? match = pattern.firstMatch(message);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
final String? version = match.group(1);
|
||||
if (version != null) {
|
||||
return version.split('/').last.split('[').first.split('(').first;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _parseMissingModule(String message) {
|
||||
// Example: "Module 'plugin_1_name' not found"
|
||||
final RegExp pattern = RegExp(r"Module '(.*?)' not found");
|
||||
final RegExpMatch? match = pattern.firstMatch(message);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
final String? version = match.group(1);
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The result of [_handleXCResultIssue].
|
||||
class _XCResultIssueHandlingResult {
|
||||
|
||||
_XCResultIssueHandlingResult({
|
||||
required this.requiresProvisioningProfile,
|
||||
required this.hasProvisioningProfileIssue,
|
||||
this.missingPlatform,
|
||||
this.duplicateModule,
|
||||
this.missingModule,
|
||||
});
|
||||
|
||||
// An issue indicates that user didn't provide the provisioning profile.
|
||||
/// An issue indicates that user didn't provide the provisioning profile.
|
||||
final bool requiresProvisioningProfile;
|
||||
|
||||
// An issue indicates that there is a provisioning profile issue.
|
||||
/// An issue indicates that there is a provisioning profile issue.
|
||||
final bool hasProvisioningProfileIssue;
|
||||
|
||||
final String? missingPlatform;
|
||||
|
||||
/// An issue indicates a module is declared twice, potentially due to being
|
||||
/// used in both Swift Package Manager and CocoaPods.
|
||||
final String? duplicateModule;
|
||||
|
||||
/// An issue indicates a module was imported but not found, potentially due
|
||||
/// to it being Swift Package Manager compatible only.
|
||||
final String? missingModule;
|
||||
}
|
||||
|
||||
const String _kResultBundlePath = 'temporary_xcresult_bundle';
|
||||
|
@ -64,6 +64,35 @@ class PlistParser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the content, converted to JSON, of the plist file located at
|
||||
/// [filePath].
|
||||
///
|
||||
/// If [filePath] points to a non-existent file or a file that's not a
|
||||
/// valid property list file, this will return null.
|
||||
String? plistJsonContent(String filePath) {
|
||||
if (!_fileSystem.isFileSync(_plutilExecutable)) {
|
||||
throw const FileNotFoundException(_plutilExecutable);
|
||||
}
|
||||
final List<String> args = <String>[
|
||||
_plutilExecutable,
|
||||
'-convert',
|
||||
'json',
|
||||
'-o',
|
||||
'-',
|
||||
filePath,
|
||||
];
|
||||
try {
|
||||
final String jsonContent = _processUtils.runSync(
|
||||
args,
|
||||
throwOnError: true,
|
||||
).stdout.trim();
|
||||
return jsonContent;
|
||||
} on ProcessException catch (error) {
|
||||
_logger.printError('$error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the string key in the given plist file with the given value.
|
||||
///
|
||||
/// If the value is null, then the key will be removed.
|
||||
|
@ -571,7 +571,15 @@ class IOSSimulator extends Device {
|
||||
deviceID: id,
|
||||
);
|
||||
if (!buildResult.success) {
|
||||
await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, globals.logger, globals.analytics);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
analytics: globals.analytics,
|
||||
fileSystem: globals.fs,
|
||||
flutterUsage: globals.flutterUsage,
|
||||
logger: globals.logger,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: app.project.parent,
|
||||
);
|
||||
throwToolExit('Could not build the application for the simulator.');
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../ios/xcode_build_settings.dart';
|
||||
import '../ios/xcodeproj.dart';
|
||||
import '../migrations/swift_package_manager_integration_migration.dart';
|
||||
import '../migrations/xcode_project_object_version_migration.dart';
|
||||
import '../migrations/xcode_script_build_phase_migration.dart';
|
||||
import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart';
|
||||
@ -85,6 +86,16 @@ Future<void> buildMacOS({
|
||||
XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger),
|
||||
FlutterApplicationMigration(flutterProject.macos, globals.logger),
|
||||
NSApplicationMainDeprecationMigration(flutterProject.macos, globals.logger),
|
||||
if (flutterProject.usesSwiftPackageManager && flutterProject.macos.flutterPluginSwiftPackageManifest.existsSync())
|
||||
SwiftPackageManagerIntegrationMigration(
|
||||
flutterProject.macos,
|
||||
SupportedPlatform.macos,
|
||||
buildInfo,
|
||||
xcodeProjectInterpreter: globals.xcodeProjectInterpreter!,
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
plistParser: globals.plistParser,
|
||||
),
|
||||
];
|
||||
|
||||
final ProjectMigration migration = ProjectMigration(migrators);
|
||||
@ -101,6 +112,11 @@ Future<void> buildMacOS({
|
||||
targetOverride: targetOverride,
|
||||
useMacOSConfig: true,
|
||||
);
|
||||
|
||||
// TODO(vashworth): Call `SwiftPackageManager.updateMinimumDeployment`
|
||||
// using MACOSX_DEPLOYMENT_TARGET once https://github.com/flutter/flutter/issues/146204
|
||||
// is fixed.
|
||||
|
||||
await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode);
|
||||
// If the xcfilelists do not exist, create empty version.
|
||||
if (!flutterProject.macos.inputFileList.existsSync()) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../base/error_handling_io.dart';
|
||||
import '../base/fingerprint.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
@ -14,20 +15,57 @@ import '../project.dart';
|
||||
Future<void> processPodsIfNeeded(
|
||||
XcodeBasedProject xcodeProject,
|
||||
String buildDirectory,
|
||||
BuildMode buildMode) async {
|
||||
BuildMode buildMode, {
|
||||
bool forceCocoaPodsOnly = false,
|
||||
}) async {
|
||||
final FlutterProject project = xcodeProject.parent;
|
||||
// Ensure that the plugin list is up to date, since hasPlugins relies on it.
|
||||
await refreshPluginsList(project, macOSPlatform: project.macos.existsSync());
|
||||
if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) {
|
||||
|
||||
// When using Swift Package Manager, the Podfile may not exist so if there
|
||||
// isn't a Podfile, skip processing pods.
|
||||
if (project.usesSwiftPackageManager && !xcodeProject.podfile.existsSync() && !forceCocoaPodsOnly) {
|
||||
return;
|
||||
}
|
||||
// If the Xcode project, Podfile, or generated xcconfig have changed since
|
||||
// last run, pods should be updated.
|
||||
// Ensure that the plugin list is up to date, since hasPlugins relies on it.
|
||||
await refreshPluginsList(
|
||||
project,
|
||||
iosPlatform: project.ios.existsSync(),
|
||||
macOSPlatform: project.macos.existsSync(),
|
||||
forceCocoaPodsOnly: forceCocoaPodsOnly,
|
||||
);
|
||||
|
||||
// If there are no plugins and if the project is a not module with an existing
|
||||
// podfile, skip processing pods
|
||||
if (!hasPlugins(project) && !(project.isModule && xcodeProject.podfile.existsSync())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If forcing the use of only CocoaPods, but the project is using Swift
|
||||
// Package Manager, print a warning that CocoaPods will be used.
|
||||
if (forceCocoaPodsOnly && project.usesSwiftPackageManager) {
|
||||
globals.logger.printWarning(
|
||||
'Swift Package Manager does not yet support this command. '
|
||||
'CocoaPods will be used instead.');
|
||||
|
||||
// If CocoaPods has been deintegrated, add it back.
|
||||
if (!xcodeProject.podfile.existsSync()) {
|
||||
await globals.cocoaPods?.setupPodfile(xcodeProject);
|
||||
}
|
||||
|
||||
// Delete Swift Package Manager manifest to invalidate fingerprinter
|
||||
ErrorHandlingFileSystem.deleteIfExists(
|
||||
xcodeProject.flutterPluginSwiftPackageManifest,
|
||||
);
|
||||
}
|
||||
|
||||
// If the Xcode project, Podfile, generated plugin Swift Package, or podhelper
|
||||
// have changed since last run, pods should be updated.
|
||||
final Fingerprinter fingerprinter = Fingerprinter(
|
||||
fingerprintPath: globals.fs.path.join(buildDirectory, 'pod_inputs.fingerprint'),
|
||||
paths: <String>[
|
||||
xcodeProject.xcodeProjectInfoFile.path,
|
||||
xcodeProject.podfile.path,
|
||||
if (xcodeProject.flutterPluginSwiftPackageManifest.existsSync())
|
||||
xcodeProject.flutterPluginSwiftPackageManifest.path,
|
||||
globals.fs.path.join(
|
||||
Cache.flutterRoot!,
|
||||
'packages',
|
||||
|
@ -21,8 +21,8 @@ import '../cache.dart';
|
||||
import '../ios/xcodeproj.dart';
|
||||
import '../migrations/cocoapods_script_symlink.dart';
|
||||
import '../migrations/cocoapods_toolchain_directory_migration.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import '../xcode_project.dart';
|
||||
|
||||
const String noCocoaPodsConsequence = '''
|
||||
CocoaPods is a package manager for iOS or macOS platform code.
|
||||
@ -166,6 +166,10 @@ class CocoaPods {
|
||||
bool dependenciesChanged = true,
|
||||
}) async {
|
||||
if (!xcodeProject.podfile.existsSync()) {
|
||||
// Swift Package Manager doesn't need Podfile, so don't error.
|
||||
if (xcodeProject.parent.usesSwiftPackageManager) {
|
||||
return false;
|
||||
}
|
||||
throwToolExit('Podfile missing');
|
||||
}
|
||||
_warnIfPodfileOutOfDate(xcodeProject);
|
||||
@ -258,6 +262,18 @@ class CocoaPods {
|
||||
addPodsDependencyToFlutterXcconfig(xcodeProject);
|
||||
return;
|
||||
}
|
||||
final File podfileTemplate = await getPodfileTemplate(
|
||||
xcodeProject,
|
||||
runnerProject,
|
||||
);
|
||||
podfileTemplate.copySync(podfile.path);
|
||||
addPodsDependencyToFlutterXcconfig(xcodeProject);
|
||||
}
|
||||
|
||||
Future<File> getPodfileTemplate(
|
||||
XcodeBasedProject xcodeProject,
|
||||
Directory runnerProject,
|
||||
) async {
|
||||
String podfileTemplateName;
|
||||
if (xcodeProject is MacOSProject) {
|
||||
podfileTemplateName = 'Podfile-macos';
|
||||
@ -268,7 +284,7 @@ class CocoaPods {
|
||||
)).containsKey('SWIFT_VERSION');
|
||||
podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc';
|
||||
}
|
||||
final File podfileTemplate = _fileSystem.file(_fileSystem.path.join(
|
||||
return _fileSystem.file(_fileSystem.path.join(
|
||||
Cache.flutterRoot!,
|
||||
'packages',
|
||||
'flutter_tools',
|
||||
@ -276,8 +292,6 @@ class CocoaPods {
|
||||
'cocoapods',
|
||||
podfileTemplateName,
|
||||
));
|
||||
podfileTemplate.copySync(podfile.path);
|
||||
addPodsDependencyToFlutterXcconfig(xcodeProject);
|
||||
}
|
||||
|
||||
/// Ensures all `Flutter/Xxx.xcconfig` files for the given Xcode-based
|
||||
@ -287,12 +301,24 @@ class CocoaPods {
|
||||
_addPodsDependencyToFlutterXcconfig(xcodeProject, 'Release');
|
||||
}
|
||||
|
||||
String includePodsXcconfig(String mode) {
|
||||
return 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
|
||||
.toLowerCase()}.xcconfig';
|
||||
}
|
||||
|
||||
bool xcconfigIncludesPods(File xcodeConfig) {
|
||||
if (xcodeConfig.existsSync()) {
|
||||
final String content = xcodeConfig.readAsStringSync();
|
||||
return content.contains('Pods/Target Support Files/Pods-');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject, String mode) {
|
||||
final File file = xcodeProject.xcodeConfigFor(mode);
|
||||
if (file.existsSync()) {
|
||||
final String content = file.readAsStringSync();
|
||||
final String includeFile = 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
|
||||
.toLowerCase()}.xcconfig';
|
||||
final String includeFile = includePodsXcconfig(mode);
|
||||
final String include = '#include? "$includeFile"';
|
||||
if (!content.contains('Pods/Target Support Files/Pods-')) {
|
||||
file.writeAsStringSync('$include\n$content', flush: true);
|
||||
|
@ -0,0 +1,257 @@
|
||||
// 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 '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import 'cocoapods.dart';
|
||||
import 'swift_package_manager.dart';
|
||||
|
||||
/// Flutter has two dependency management solutions for iOS and macOS
|
||||
/// applications: CocoaPods and Swift Package Manager. They may be used
|
||||
/// individually or together. This class handles setting up required files and
|
||||
/// project settings for the dependency manager(s) being used.
|
||||
class DarwinDependencyManagement {
|
||||
DarwinDependencyManagement({
|
||||
required FlutterProject project,
|
||||
required List<Plugin> plugins,
|
||||
required CocoaPods cocoapods,
|
||||
required SwiftPackageManager swiftPackageManager,
|
||||
required FileSystem fileSystem,
|
||||
required Logger logger,
|
||||
}) : _project = project,
|
||||
_plugins = plugins,
|
||||
_cocoapods = cocoapods,
|
||||
_swiftPackageManager = swiftPackageManager,
|
||||
_fileSystem = fileSystem,
|
||||
_logger = logger;
|
||||
|
||||
final FlutterProject _project;
|
||||
final List<Plugin> _plugins;
|
||||
final CocoaPods _cocoapods;
|
||||
final SwiftPackageManager _swiftPackageManager;
|
||||
final FileSystem _fileSystem;
|
||||
final Logger _logger;
|
||||
|
||||
/// Generates/updates required files and project settings for Darwin
|
||||
/// Dependency Managers (CocoaPods and Swift Package Manager). Projects may
|
||||
/// use only CocoaPods (if no SPM compatible dependencies or SPM has been
|
||||
/// disabled), only Swift Package Manager (if no CocoaPod dependencies), or
|
||||
/// both. This only generates files for the manager(s) being used.
|
||||
///
|
||||
/// CocoaPods requires a Podfile and certain values in the Flutter xcconfig
|
||||
/// files.
|
||||
///
|
||||
/// Swift Package Manager requires a generated Package.swift and certain
|
||||
/// settings in the Xcode project's project.pbxproj and xcscheme (done later
|
||||
/// before build).
|
||||
Future<void> setUp({
|
||||
required SupportedPlatform platform,
|
||||
}) async {
|
||||
if (platform != SupportedPlatform.ios &&
|
||||
platform != SupportedPlatform.macos) {
|
||||
throwToolExit(
|
||||
'The platform ${platform.name} is incompatible with Darwin Dependency Managers. Only iOS and macOS are allowed.',
|
||||
);
|
||||
}
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios
|
||||
? _project.ios
|
||||
: _project.macos;
|
||||
if (_project.usesSwiftPackageManager) {
|
||||
await _swiftPackageManager.generatePluginsSwiftPackage(
|
||||
_plugins,
|
||||
platform,
|
||||
xcodeProject,
|
||||
);
|
||||
} else if (xcodeProject.flutterPluginSwiftPackageInProjectSettings) {
|
||||
// If Swift Package Manager is not enabled but the project is already
|
||||
// integrated for Swift Package Manager, pass no plugins to the generator.
|
||||
// This will still generate the required Package.swift, but it will have
|
||||
// no dependencies.
|
||||
await _swiftPackageManager.generatePluginsSwiftPackage(
|
||||
<Plugin>[],
|
||||
platform,
|
||||
xcodeProject,
|
||||
);
|
||||
}
|
||||
|
||||
// Skip updating Podfile if project is a module, since it will use a
|
||||
// different module-specific Podfile.
|
||||
if (_project.isModule) {
|
||||
return;
|
||||
}
|
||||
final (:int totalCount, :int swiftPackageCount, :int podCount) = await _evaluatePluginsAndPrintWarnings(
|
||||
platform: platform,
|
||||
xcodeProject: xcodeProject,
|
||||
);
|
||||
|
||||
final bool useCocoapods;
|
||||
if (_project.usesSwiftPackageManager) {
|
||||
useCocoapods = _usingCocoaPodsPlugin(
|
||||
pluginCount: totalCount,
|
||||
swiftPackageCount: swiftPackageCount,
|
||||
cocoapodCount: podCount,
|
||||
);
|
||||
} else {
|
||||
// When Swift Package Manager is not enabled, set up Podfile if plugins
|
||||
// is not empty, regardless of if plugins are CocoaPod compatible. This
|
||||
// is done because `processPodsIfNeeded` uses `hasPlugins` to determine
|
||||
// whether to run.
|
||||
useCocoapods = _plugins.isNotEmpty;
|
||||
}
|
||||
if (useCocoapods) {
|
||||
await _cocoapods.setupPodfile(xcodeProject);
|
||||
}
|
||||
/// The user may have a custom maintained Podfile that they're running `pod install`
|
||||
/// on themselves.
|
||||
else if (xcodeProject.podfile.existsSync() && xcodeProject.podfileLock.existsSync()) {
|
||||
_cocoapods.addPodsDependencyToFlutterXcconfig(xcodeProject);
|
||||
}
|
||||
}
|
||||
|
||||
bool _usingCocoaPodsPlugin({
|
||||
required int pluginCount,
|
||||
required int swiftPackageCount,
|
||||
required int cocoapodCount,
|
||||
}) {
|
||||
if (_project.usesSwiftPackageManager) {
|
||||
if (pluginCount == swiftPackageCount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return cocoapodCount > 0;
|
||||
}
|
||||
|
||||
/// Returns count of total number of plugins, number of Swift Package Manager
|
||||
/// compatible plugins, and number of CocoaPods compatible plugins. A plugin
|
||||
/// can be both Swift Package Manager and CocoaPods compatible.
|
||||
///
|
||||
/// Prints warnings when using a plugin incompatible with the available Darwin
|
||||
/// Dependency Manager (Swift Package Manager or CocoaPods).
|
||||
///
|
||||
/// Prints message prompting the user to deintegrate CocoaPods if using all
|
||||
/// Swift Package plugins.
|
||||
Future<({int totalCount, int swiftPackageCount, int podCount})> _evaluatePluginsAndPrintWarnings({
|
||||
required SupportedPlatform platform,
|
||||
required XcodeBasedProject xcodeProject,
|
||||
}) async {
|
||||
int pluginCount = 0;
|
||||
int swiftPackageCount = 0;
|
||||
int cocoapodCount = 0;
|
||||
for (final Plugin plugin in _plugins) {
|
||||
if (plugin.platforms[platform.name] == null) {
|
||||
continue;
|
||||
}
|
||||
final String? swiftPackagePath = plugin.pluginSwiftPackageManifestPath(
|
||||
_fileSystem,
|
||||
platform.name,
|
||||
);
|
||||
final bool swiftPackageManagerCompatible = swiftPackagePath != null &&
|
||||
_fileSystem.file(swiftPackagePath).existsSync();
|
||||
|
||||
final String? podspecPath = plugin.pluginPodspecPath(
|
||||
_fileSystem,
|
||||
platform.name,
|
||||
);
|
||||
final bool cocoaPodsCompatible =
|
||||
podspecPath != null && _fileSystem.file(podspecPath).existsSync();
|
||||
|
||||
// If a plugin is missing both a Package.swift and Podspec, it won't be
|
||||
// included by either Swift Package Manager or Cocoapods. This can happen
|
||||
// when a plugin doesn't have native platform code.
|
||||
// For example, image_picker_macos only uses dart code.
|
||||
if (!swiftPackageManagerCompatible && !cocoaPodsCompatible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pluginCount += 1;
|
||||
if (swiftPackageManagerCompatible) {
|
||||
swiftPackageCount += 1;
|
||||
}
|
||||
if (cocoaPodsCompatible) {
|
||||
cocoapodCount += 1;
|
||||
}
|
||||
|
||||
// If not using Swift Package Manager and plugin does not have podspec
|
||||
// but does have a Package.swift, throw an error. Otherwise, it'll error
|
||||
// when it builds.
|
||||
if (!_project.usesSwiftPackageManager &&
|
||||
!cocoaPodsCompatible &&
|
||||
swiftPackageManagerCompatible) {
|
||||
throwToolExit(
|
||||
'Plugin ${plugin.name} is only Swift Package Manager compatible. Try '
|
||||
'enabling Swift Package Manager by running '
|
||||
'"flutter config --enable-swift-package-manager" or remove the '
|
||||
'plugin as a dependency.');
|
||||
}
|
||||
}
|
||||
|
||||
// Only show warnings to remove CocoaPods if the project is using Swift
|
||||
// Package Manager, has already been migrated to have SPM integration, and
|
||||
// all plugins are Swift Packages.
|
||||
if (_project.usesSwiftPackageManager &&
|
||||
xcodeProject.flutterPluginSwiftPackageInProjectSettings &&
|
||||
pluginCount == swiftPackageCount &&
|
||||
swiftPackageCount != 0) {
|
||||
final bool podfileExists = xcodeProject.podfile.existsSync();
|
||||
if (podfileExists) {
|
||||
// If all plugins are Swift Packages and the Podfile matches the
|
||||
// default template, recommend pod deintegration.
|
||||
final File podfileTemplate = await _cocoapods.getPodfileTemplate(
|
||||
xcodeProject,
|
||||
xcodeProject.xcodeProject,
|
||||
);
|
||||
|
||||
final String configWarning = '${_podIncludeInConfigWarning(xcodeProject, 'Debug')}'
|
||||
'${_podIncludeInConfigWarning(xcodeProject, 'Release')}';
|
||||
|
||||
if (xcodeProject.podfile.readAsStringSync() ==
|
||||
podfileTemplate.readAsStringSync()) {
|
||||
_logger.printWarning(
|
||||
'All plugins found for ${platform.name} are Swift Packages, but your '
|
||||
'project still has CocoaPods integration. To remove CocoaPods '
|
||||
'integration, complete the following steps:\n'
|
||||
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
|
||||
' * Also in the ${platform.name}/ directory, delete the Podfile\n'
|
||||
'$configWarning\n'
|
||||
"Removing CocoaPods integration will improve the project's build time.");
|
||||
} else {
|
||||
// If all plugins are Swift Packages, but the Podfile has custom logic,
|
||||
// recommend migrating manually.
|
||||
_logger.printWarning(
|
||||
'All plugins found for ${platform.name} are Swift Packages, but your '
|
||||
'project still has CocoaPods integration. Your project uses a '
|
||||
'non-standard Podfile and will need to be migrated to Swift Package '
|
||||
'Manager manually. Some steps you may need to complete include:\n'
|
||||
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
|
||||
' * Transition any Pod dependencies to Swift Package equivalents. '
|
||||
'See https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app\n'
|
||||
' * Transition any custom logic\n'
|
||||
'$configWarning\n'
|
||||
"Removing CocoaPods integration will improve the project's build time.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
totalCount: pluginCount,
|
||||
swiftPackageCount: swiftPackageCount,
|
||||
podCount: cocoapodCount,
|
||||
);
|
||||
}
|
||||
|
||||
String _podIncludeInConfigWarning(XcodeBasedProject xcodeProject, String mode) {
|
||||
final File xcconfigFile = xcodeProject.xcodeConfigFor(mode);
|
||||
final bool configIncludesPods = _cocoapods.xcconfigIncludesPods(xcconfigFile);
|
||||
if (configIncludesPods) {
|
||||
return ' * Remove the include to '
|
||||
'"${_cocoapods.includePodsXcconfig(mode)}" in your '
|
||||
'${xcconfigFile.parent.parent.basename}/${xcconfigFile.parent.basename}/${xcconfigFile.basename}\n';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
196
packages/flutter_tools/lib/src/macos/swift_package_manager.dart
Normal file
196
packages/flutter_tools/lib/src/macos/swift_package_manager.dart
Normal file
@ -0,0 +1,196 @@
|
||||
// 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 '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/template.dart';
|
||||
import '../base/version.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import 'swift_packages.dart';
|
||||
|
||||
/// Swift Package Manager is a dependency management solution for iOS and macOS
|
||||
/// applications.
|
||||
///
|
||||
/// See also:
|
||||
/// * https://www.swift.org/documentation/package-manager/ - documentation on
|
||||
/// Swift Package Manager.
|
||||
/// * https://developer.apple.com/documentation/packagedescription/package -
|
||||
/// documentation on Swift Package Manager manifest file, Package.swift.
|
||||
class SwiftPackageManager {
|
||||
const SwiftPackageManager({
|
||||
required FileSystem fileSystem,
|
||||
required TemplateRenderer templateRenderer,
|
||||
}) : _fileSystem = fileSystem,
|
||||
_templateRenderer = templateRenderer;
|
||||
|
||||
final FileSystem _fileSystem;
|
||||
final TemplateRenderer _templateRenderer;
|
||||
|
||||
static const String _defaultFlutterPluginsSwiftPackageName = 'FlutterGeneratedPluginSwiftPackage';
|
||||
|
||||
static final SwiftPackageSupportedPlatform _iosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform(
|
||||
platform: SwiftPackagePlatform.ios,
|
||||
version: Version(12, 0, null),
|
||||
);
|
||||
|
||||
static final SwiftPackageSupportedPlatform _macosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform(
|
||||
platform: SwiftPackagePlatform.macos,
|
||||
version: Version(10, 14, null),
|
||||
);
|
||||
|
||||
/// Creates a Swift Package called 'FlutterGeneratedPluginSwiftPackage' that
|
||||
/// has dependencies on Flutter plugins that are compatible with Swift
|
||||
/// Package Manager.
|
||||
Future<void> generatePluginsSwiftPackage(
|
||||
List<Plugin> plugins,
|
||||
SupportedPlatform platform,
|
||||
XcodeBasedProject project,
|
||||
) async {
|
||||
_validatePlatform(platform);
|
||||
|
||||
final (
|
||||
List<SwiftPackagePackageDependency> packageDependencies,
|
||||
List<SwiftPackageTargetDependency> targetDependencies
|
||||
) = _dependenciesForPlugins(plugins, platform);
|
||||
|
||||
// If there aren't any Swift Package plugins and the project hasn't been
|
||||
// migrated yet, don't generate a Swift package or migrate the app since
|
||||
// it's not needed. If the project has already been migrated, regenerate
|
||||
// the Package.swift even if there are no dependencies in case there
|
||||
// were dependencies previously.
|
||||
if (packageDependencies.isEmpty && !project.flutterPluginSwiftPackageInProjectSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SwiftPackageSupportedPlatform swiftSupportedPlatform;
|
||||
if (platform == SupportedPlatform.ios) {
|
||||
swiftSupportedPlatform = _iosSwiftPackageSupportedPlatform;
|
||||
} else {
|
||||
swiftSupportedPlatform = _macosSwiftPackageSupportedPlatform;
|
||||
}
|
||||
|
||||
// FlutterGeneratedPluginSwiftPackage must be statically linked to ensure
|
||||
// any dynamic dependencies are linked to Runner and prevent undefined symbols.
|
||||
final SwiftPackageProduct generatedProduct = SwiftPackageProduct(
|
||||
name: _defaultFlutterPluginsSwiftPackageName,
|
||||
targets: <String>[_defaultFlutterPluginsSwiftPackageName],
|
||||
libraryType: SwiftPackageLibraryType.static,
|
||||
);
|
||||
|
||||
final SwiftPackageTarget generatedTarget = SwiftPackageTarget.defaultTarget(
|
||||
name: _defaultFlutterPluginsSwiftPackageName,
|
||||
dependencies: targetDependencies,
|
||||
);
|
||||
|
||||
final SwiftPackage pluginsPackage = SwiftPackage(
|
||||
manifest: project.flutterPluginSwiftPackageManifest,
|
||||
name: _defaultFlutterPluginsSwiftPackageName,
|
||||
platforms: <SwiftPackageSupportedPlatform>[swiftSupportedPlatform],
|
||||
products: <SwiftPackageProduct>[generatedProduct],
|
||||
dependencies: packageDependencies,
|
||||
targets: <SwiftPackageTarget>[generatedTarget],
|
||||
templateRenderer: _templateRenderer,
|
||||
);
|
||||
pluginsPackage.createSwiftPackage();
|
||||
}
|
||||
|
||||
(List<SwiftPackagePackageDependency>, List<SwiftPackageTargetDependency>) _dependenciesForPlugins(
|
||||
List<Plugin> plugins,
|
||||
SupportedPlatform platform,
|
||||
) {
|
||||
final List<SwiftPackagePackageDependency> packageDependencies =
|
||||
<SwiftPackagePackageDependency>[];
|
||||
final List<SwiftPackageTargetDependency> targetDependencies =
|
||||
<SwiftPackageTargetDependency>[];
|
||||
|
||||
for (final Plugin plugin in plugins) {
|
||||
final String? pluginSwiftPackageManifestPath = plugin.pluginSwiftPackageManifestPath(
|
||||
_fileSystem,
|
||||
platform.name,
|
||||
);
|
||||
if (plugin.platforms[platform.name] == null ||
|
||||
pluginSwiftPackageManifestPath == null ||
|
||||
!_fileSystem.file(pluginSwiftPackageManifestPath).existsSync()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
packageDependencies.add(SwiftPackagePackageDependency(
|
||||
name: plugin.name,
|
||||
path: _fileSystem.file(pluginSwiftPackageManifestPath).parent.path,
|
||||
));
|
||||
|
||||
// The target dependency product name is hyphen separated because it's
|
||||
// the dependency's library name, which Swift Package Manager will
|
||||
// automatically use as the CFBundleIdentifier if linked dynamically. The
|
||||
// CFBundleIdentifier cannot contain underscores.
|
||||
targetDependencies.add(SwiftPackageTargetDependency.product(
|
||||
name: plugin.name.replaceAll('_', '-'),
|
||||
packageName: plugin.name,
|
||||
));
|
||||
}
|
||||
return (packageDependencies, targetDependencies);
|
||||
}
|
||||
|
||||
/// Validates the platform is either iOS or macOS, otherwise throw an error.
|
||||
static void _validatePlatform(SupportedPlatform platform) {
|
||||
if (platform != SupportedPlatform.ios &&
|
||||
platform != SupportedPlatform.macos) {
|
||||
throwToolExit(
|
||||
'The platform ${platform.name} is not compatible with Swift Package Manager. '
|
||||
'Only iOS and macOS are allowed.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// If the project's IPHONEOS_DEPLOYMENT_TARGET/MACOSX_DEPLOYMENT_TARGET is
|
||||
/// higher than the FlutterGeneratedPluginSwiftPackage's default
|
||||
/// SupportedPlatform, increase the SupportedPlatform to match the project's
|
||||
/// deployment target.
|
||||
///
|
||||
/// This is done for the use case of a plugin requiring a higher iOS/macOS
|
||||
/// version than FlutterGeneratedPluginSwiftPackage.
|
||||
///
|
||||
/// Swift Package Manager emits an error if a dependency isn’t compatible
|
||||
/// with the top-level package’s deployment version. The deployment target of
|
||||
/// a package’s dependencies must be lower than or equal to the top-level
|
||||
/// package’s deployment target version for a particular platform.
|
||||
///
|
||||
/// To still be able to use the plugin, the user can increase the Xcode
|
||||
/// project's iOS/macOS deployment target and this will then increase the
|
||||
/// deployment target for FlutterGeneratedPluginSwiftPackage.
|
||||
static void updateMinimumDeployment({
|
||||
required XcodeBasedProject project,
|
||||
required SupportedPlatform platform,
|
||||
required String deploymentTarget,
|
||||
}) {
|
||||
final Version? projectDeploymentTargetVersion = Version.parse(deploymentTarget);
|
||||
final SwiftPackageSupportedPlatform defaultPlatform;
|
||||
final SwiftPackagePlatform packagePlatform;
|
||||
if (platform == SupportedPlatform.ios) {
|
||||
defaultPlatform = _iosSwiftPackageSupportedPlatform;
|
||||
packagePlatform = SwiftPackagePlatform.ios;
|
||||
} else {
|
||||
defaultPlatform = _macosSwiftPackageSupportedPlatform;
|
||||
packagePlatform = SwiftPackagePlatform.macos;
|
||||
}
|
||||
|
||||
if (projectDeploymentTargetVersion == null ||
|
||||
projectDeploymentTargetVersion <= defaultPlatform.version ||
|
||||
!project.flutterPluginSwiftPackageManifest.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String manifestContents = project.flutterPluginSwiftPackageManifest.readAsStringSync();
|
||||
final String oldSupportedPlatform = defaultPlatform.format();
|
||||
final String newSupportedPlatform = SwiftPackageSupportedPlatform(
|
||||
platform: packagePlatform,
|
||||
version: projectDeploymentTargetVersion,
|
||||
).format();
|
||||
|
||||
project.flutterPluginSwiftPackageManifest.writeAsStringSync(
|
||||
manifestContents.replaceFirst(oldSupportedPlatform, newSupportedPlatform),
|
||||
);
|
||||
}
|
||||
}
|
403
packages/flutter_tools/lib/src/macos/swift_packages.dart
Normal file
403
packages/flutter_tools/lib/src/macos/swift_packages.dart
Normal file
@ -0,0 +1,403 @@
|
||||
// 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 '../base/file_system.dart';
|
||||
import '../base/template.dart';
|
||||
import '../base/version.dart';
|
||||
|
||||
/// Swift toolchain version included with Xcode 15.0.
|
||||
const String minimumSwiftToolchainVersion = '5.9';
|
||||
|
||||
const String _swiftPackageTemplate = '''
|
||||
// swift-tools-version: {{swiftToolsVersion}}
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "{{packageName}}",
|
||||
{{#platforms}}
|
||||
platforms: [
|
||||
{{platforms}}
|
||||
],
|
||||
{{/platforms}}
|
||||
products: [
|
||||
{{products}}
|
||||
],
|
||||
dependencies: [
|
||||
{{dependencies}}
|
||||
],
|
||||
targets: [
|
||||
{{targets}}
|
||||
]
|
||||
)
|
||||
''';
|
||||
|
||||
const String _swiftPackageSourceTemplate = '''
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
''';
|
||||
|
||||
const String _singleIndent = ' ';
|
||||
const String _doubleIndent = '$_singleIndent$_singleIndent';
|
||||
|
||||
/// A Swift Package is reusable code that can be shared across projects and
|
||||
/// with other developers in iOS and macOS applications. A Swift Package
|
||||
/// requires a Package.swift. This class handles the formatting and creation of
|
||||
/// a Package.swift.
|
||||
///
|
||||
/// See https://developer.apple.com/documentation/packagedescription/package
|
||||
/// for more information about Swift Packages and Package.swift.
|
||||
class SwiftPackage {
|
||||
SwiftPackage({
|
||||
required File manifest,
|
||||
required String name,
|
||||
required List<SwiftPackageSupportedPlatform> platforms,
|
||||
required List<SwiftPackageProduct> products,
|
||||
required List<SwiftPackagePackageDependency> dependencies,
|
||||
required List<SwiftPackageTarget> targets,
|
||||
required TemplateRenderer templateRenderer,
|
||||
}) : _manifest = manifest,
|
||||
_name = name,
|
||||
_platforms = platforms,
|
||||
_products = products,
|
||||
_dependencies = dependencies,
|
||||
_targets = targets,
|
||||
_templateRenderer = templateRenderer;
|
||||
|
||||
/// [File] for Package.swift.
|
||||
final File _manifest;
|
||||
|
||||
/// The name of the Swift package.
|
||||
final String _name;
|
||||
|
||||
/// The list of minimum versions for platforms supported by the package.
|
||||
final List<SwiftPackageSupportedPlatform> _platforms;
|
||||
|
||||
/// The list of products that this package vends and that clients can use.
|
||||
final List<SwiftPackageProduct> _products;
|
||||
|
||||
/// The list of package dependencies.
|
||||
final List<SwiftPackagePackageDependency> _dependencies;
|
||||
|
||||
/// The list of targets that are part of this package.
|
||||
final List<SwiftPackageTarget> _targets;
|
||||
|
||||
final TemplateRenderer _templateRenderer;
|
||||
|
||||
/// Context for the [_swiftPackageTemplate] template.
|
||||
Map<String, Object> get _templateContext {
|
||||
return <String, Object>{
|
||||
'swiftToolsVersion': minimumSwiftToolchainVersion,
|
||||
'packageName': _name,
|
||||
// Supported platforms can't be empty, so only include if not null.
|
||||
'platforms': _formatPlatforms() ?? false,
|
||||
'products': _formatProducts(),
|
||||
'dependencies': _formatDependencies(),
|
||||
'targets': _formatTargets(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a Package.swift using settings from [_templateContext].
|
||||
void createSwiftPackage() {
|
||||
// Swift Packages require at least one source file per non-binary target,
|
||||
// whether it be in Swift or Objective C. If the target does not have any
|
||||
// files yet, create an empty Swift file.
|
||||
for (final SwiftPackageTarget target in _targets) {
|
||||
if (target.targetType == SwiftPackageTargetType.binaryTarget) {
|
||||
continue;
|
||||
}
|
||||
final Directory targetDirectory = _manifest.parent
|
||||
.childDirectory('Sources')
|
||||
.childDirectory(target.name);
|
||||
if (!targetDirectory.existsSync() || targetDirectory.listSync().isEmpty) {
|
||||
final File requiredSwiftFile = targetDirectory.childFile(
|
||||
'${target.name}.swift',
|
||||
);
|
||||
requiredSwiftFile.createSync(recursive: true);
|
||||
requiredSwiftFile.writeAsStringSync(_swiftPackageSourceTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
final String renderedTemplate = _templateRenderer.renderString(
|
||||
_swiftPackageTemplate,
|
||||
_templateContext,
|
||||
);
|
||||
_manifest.createSync(recursive: true);
|
||||
_manifest.writeAsStringSync(renderedTemplate);
|
||||
}
|
||||
|
||||
String? _formatPlatforms() {
|
||||
if (_platforms.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final List<String> platformStrings = _platforms
|
||||
.map((SwiftPackageSupportedPlatform platform) => platform.format())
|
||||
.toList();
|
||||
return platformStrings.join(',\n$_doubleIndent');
|
||||
}
|
||||
|
||||
String _formatProducts() {
|
||||
if (_products.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
final List<String> libraries = _products
|
||||
.map((SwiftPackageProduct product) => product.format())
|
||||
.toList();
|
||||
return libraries.join(',\n$_doubleIndent');
|
||||
}
|
||||
|
||||
String _formatDependencies() {
|
||||
if (_dependencies.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
final List<String> packages = _dependencies
|
||||
.map((SwiftPackagePackageDependency dependency) => dependency.format())
|
||||
.toList();
|
||||
return packages.join(',\n$_doubleIndent');
|
||||
}
|
||||
|
||||
String _formatTargets() {
|
||||
if (_targets.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
final List<String> targetList =
|
||||
_targets.map((SwiftPackageTarget target) => target.format()).toList();
|
||||
return targetList.join(',\n$_doubleIndent');
|
||||
}
|
||||
}
|
||||
|
||||
enum SwiftPackagePlatform {
|
||||
ios(name: '.iOS'),
|
||||
macos(name: '.macOS'),
|
||||
tvos(name: '.tvOS'),
|
||||
watchos(name: '.watchOS');
|
||||
|
||||
const SwiftPackagePlatform({required this.name});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// A platform that the Swift package supports.
|
||||
///
|
||||
/// Representation of SupportedPlatform from
|
||||
/// https://developer.apple.com/documentation/packagedescription/supportedplatform.
|
||||
class SwiftPackageSupportedPlatform {
|
||||
SwiftPackageSupportedPlatform({
|
||||
required this.platform,
|
||||
required this.version,
|
||||
});
|
||||
|
||||
final SwiftPackagePlatform platform;
|
||||
final Version version;
|
||||
|
||||
String format() {
|
||||
// platforms: [
|
||||
// .macOS("10.14"),
|
||||
// .iOS("12.0"),
|
||||
// ],
|
||||
return '${platform.name}("$version")';
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of library linking.
|
||||
///
|
||||
/// Representation of Product.Library.LibraryType from
|
||||
/// https://developer.apple.com/documentation/packagedescription/product/library/librarytype.
|
||||
enum SwiftPackageLibraryType {
|
||||
dynamic(name: '.dynamic'),
|
||||
static(name: '.static');
|
||||
|
||||
const SwiftPackageLibraryType({required this.name});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// An externally visible build artifact that's available to clients of the
|
||||
/// package.
|
||||
///
|
||||
/// Representation of Product from
|
||||
/// https://developer.apple.com/documentation/packagedescription/product.
|
||||
class SwiftPackageProduct {
|
||||
SwiftPackageProduct({
|
||||
required this.name,
|
||||
required this.targets,
|
||||
this.libraryType,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final SwiftPackageLibraryType? libraryType;
|
||||
final List<String> targets;
|
||||
|
||||
String format() {
|
||||
// products: [
|
||||
// .library(name: "FlutterGeneratedPluginSwiftPackage", targets: ["FlutterGeneratedPluginSwiftPackage"]),
|
||||
// .library(name: "FlutterDependenciesPackage", type: .dynamic, targets: ["FlutterDependenciesPackage"]),
|
||||
// ],
|
||||
String targetsString = '';
|
||||
if (targets.isNotEmpty) {
|
||||
final List<String> quotedTargets =
|
||||
targets.map((String target) => '"$target"').toList();
|
||||
targetsString = ', targets: [${quotedTargets.join(', ')}]';
|
||||
}
|
||||
String libraryTypeString = '';
|
||||
if (libraryType != null) {
|
||||
libraryTypeString = ', type: ${libraryType!.name}';
|
||||
}
|
||||
return '.library(name: "$name"$libraryTypeString$targetsString)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A package dependency of a Swift package.
|
||||
///
|
||||
/// Representation of Package.Dependency from
|
||||
/// https://developer.apple.com/documentation/packagedescription/package/dependency.
|
||||
class SwiftPackagePackageDependency {
|
||||
SwiftPackagePackageDependency({
|
||||
required this.name,
|
||||
required this.path,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String path;
|
||||
|
||||
String format() {
|
||||
// dependencies: [
|
||||
// .package(name: "image_picker_ios", path: "/path/to/packages/image_picker/image_picker_ios/ios/image_picker_ios"),
|
||||
// ],
|
||||
return '.package(name: "$name", path: "$path")';
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of Target constructor.
|
||||
///
|
||||
/// See https://developer.apple.com/documentation/packagedescription/target for
|
||||
/// more information.
|
||||
enum SwiftPackageTargetType {
|
||||
target(name: '.target'),
|
||||
binaryTarget(name: '.binaryTarget');
|
||||
|
||||
const SwiftPackageTargetType({required this.name});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// A building block of a Swift Package that contains a set of source files
|
||||
/// that Swift Package Manager compiles into a module.
|
||||
///
|
||||
/// Representation of Target from
|
||||
/// https://developer.apple.com/documentation/packagedescription/target.
|
||||
class SwiftPackageTarget {
|
||||
SwiftPackageTarget.defaultTarget({
|
||||
required this.name,
|
||||
this.dependencies,
|
||||
}) : path = null,
|
||||
targetType = SwiftPackageTargetType.target;
|
||||
|
||||
SwiftPackageTarget.binaryTarget({
|
||||
required this.name,
|
||||
required String relativePath,
|
||||
}) : path = relativePath,
|
||||
dependencies = null,
|
||||
targetType = SwiftPackageTargetType.binaryTarget;
|
||||
|
||||
final String name;
|
||||
final String? path;
|
||||
final List<SwiftPackageTargetDependency>? dependencies;
|
||||
final SwiftPackageTargetType targetType;
|
||||
|
||||
String format() {
|
||||
// targets: [
|
||||
// .binaryTarget(
|
||||
// name: "Flutter",
|
||||
// path: "Flutter.xcframework"
|
||||
// ),
|
||||
// .target(
|
||||
// name: "FlutterGeneratedPluginSwiftPackage",
|
||||
// dependencies: [
|
||||
// .target(name: "Flutter"),
|
||||
// .product(name: "image_picker_ios", package: "image_picker_ios")
|
||||
// ]
|
||||
// ),
|
||||
// ]
|
||||
const String targetIndent = _doubleIndent;
|
||||
const String targetDetailsIndent = '$_doubleIndent$_singleIndent';
|
||||
|
||||
final List<String> targetDetails = <String>[];
|
||||
|
||||
final String nameString = 'name: "$name"';
|
||||
targetDetails.add(nameString);
|
||||
|
||||
if (path != null) {
|
||||
final String pathString = 'path: "$path"';
|
||||
targetDetails.add(pathString);
|
||||
}
|
||||
|
||||
if (dependencies != null && dependencies!.isNotEmpty) {
|
||||
final List<String> targetDependencies = dependencies!
|
||||
.map((SwiftPackageTargetDependency dependency) => dependency.format())
|
||||
.toList();
|
||||
final String dependenciesString = '''
|
||||
dependencies: [
|
||||
${targetDependencies.join(",\n")}
|
||||
$targetDetailsIndent]''';
|
||||
targetDetails.add(dependenciesString);
|
||||
}
|
||||
|
||||
return '''
|
||||
${targetType.name}(
|
||||
$targetDetailsIndent${targetDetails.join(",\n$targetDetailsIndent")}
|
||||
$targetIndent)''';
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of Target.Dependency constructor.
|
||||
///
|
||||
/// See https://developer.apple.com/documentation/packagedescription/target/dependency
|
||||
/// for more information.
|
||||
enum SwiftPackageTargetDependencyType {
|
||||
product(name: '.product'),
|
||||
target(name: '.target');
|
||||
|
||||
const SwiftPackageTargetDependencyType({required this.name});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
/// A dependency for the Target on a product from a package dependency or from
|
||||
/// another Target in the same package.
|
||||
///
|
||||
/// Representation of Target.Dependency from
|
||||
/// https://developer.apple.com/documentation/packagedescription/target/dependency.
|
||||
class SwiftPackageTargetDependency {
|
||||
SwiftPackageTargetDependency.product({
|
||||
required this.name,
|
||||
required String packageName,
|
||||
}) : package = packageName,
|
||||
dependencyType = SwiftPackageTargetDependencyType.product;
|
||||
|
||||
SwiftPackageTargetDependency.target({
|
||||
required this.name,
|
||||
}) : package = null,
|
||||
dependencyType = SwiftPackageTargetDependencyType.target;
|
||||
|
||||
final String name;
|
||||
final String? package;
|
||||
final SwiftPackageTargetDependencyType dependencyType;
|
||||
|
||||
String format() {
|
||||
// dependencies: [
|
||||
// .target(name: "Flutter"),
|
||||
// .product(name: "image_picker_ios", package: "image_picker_ios")
|
||||
// ]
|
||||
if (dependencyType == SwiftPackageTargetDependencyType.product) {
|
||||
return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name", package: "$package")';
|
||||
}
|
||||
return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name")';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -397,6 +397,51 @@ class Plugin {
|
||||
/// Whether this plugin is a direct dependency of the app.
|
||||
/// If [false], the plugin is a dependency of another plugin.
|
||||
final bool isDirectDependency;
|
||||
|
||||
/// Expected path to the plugin's Package.swift. Returns null if the plugin
|
||||
/// does not support the [platform] or the [platform] is not iOS or macOS.
|
||||
String? pluginSwiftPackageManifestPath(
|
||||
FileSystem fileSystem,
|
||||
String platform,
|
||||
) {
|
||||
final String? platformDirectoryName = _darwinPluginDirectoryName(platform);
|
||||
if (platformDirectoryName == null) {
|
||||
return null;
|
||||
}
|
||||
return fileSystem.path.join(
|
||||
path,
|
||||
platformDirectoryName,
|
||||
name,
|
||||
'Package.swift',
|
||||
);
|
||||
}
|
||||
|
||||
/// Expected path to the plugin's podspec. Returns null if the plugin does
|
||||
/// not support the [platform] or the [platform] is not iOS or macOS.
|
||||
String? pluginPodspecPath(FileSystem fileSystem, String platform) {
|
||||
final String? platformDirectoryName = _darwinPluginDirectoryName(platform);
|
||||
if (platformDirectoryName == null) {
|
||||
return null;
|
||||
}
|
||||
return fileSystem.path.join(path, platformDirectoryName, '$name.podspec');
|
||||
}
|
||||
|
||||
String? _darwinPluginDirectoryName(String platform) {
|
||||
final PluginPlatform? platformPlugin = platforms[platform];
|
||||
if (platformPlugin == null ||
|
||||
(platform != IOSPlugin.kConfigKey &&
|
||||
platform != MacOSPlugin.kConfigKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iOS and macOS code can be shared in "darwin" directory, otherwise
|
||||
// respectively in "ios" or "macos" directories.
|
||||
if (platformPlugin is DarwinPlugin &&
|
||||
(platformPlugin as DarwinPlugin).sharedDarwinSource) {
|
||||
return 'darwin';
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata associated with the resolution of a platform interface of a plugin.
|
||||
|
@ -21,6 +21,7 @@ import 'features.dart';
|
||||
import 'flutter_manifest.dart';
|
||||
import 'flutter_plugins.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'macos/xcode.dart';
|
||||
import 'platform_plugins.dart';
|
||||
import 'project_validator_result.dart';
|
||||
import 'template.dart';
|
||||
@ -31,14 +32,20 @@ export 'xcode_project.dart';
|
||||
|
||||
/// Enum for each officially supported platform.
|
||||
enum SupportedPlatform {
|
||||
android,
|
||||
ios,
|
||||
linux,
|
||||
macos,
|
||||
web,
|
||||
windows,
|
||||
fuchsia,
|
||||
root, // Special platform to represent the root project directory
|
||||
android(name: 'android'),
|
||||
ios(name: 'ios'),
|
||||
linux(name: 'linux'),
|
||||
macos(name: 'macos'),
|
||||
web(name: 'web'),
|
||||
windows(name: 'windows'),
|
||||
fuchsia(name: 'fuchsia'),
|
||||
root(name: 'root'); // Special platform to represent the root project directory
|
||||
|
||||
const SupportedPlatform({
|
||||
required this.name,
|
||||
});
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
class FlutterProjectFactory {
|
||||
@ -261,6 +268,24 @@ class FlutterProject {
|
||||
/// True if this project has an example application.
|
||||
bool get hasExampleApp => _exampleDirectory(directory).existsSync();
|
||||
|
||||
/// True if this project doesn't have Swift Package Manager disabled in the
|
||||
/// pubspec, has either an iOS or macOS platform implementation, is not a
|
||||
/// module project, Xcode is 15 or greater, and the Swift Package Manager
|
||||
/// feature is enabled.
|
||||
bool get usesSwiftPackageManager {
|
||||
if (!manifest.disabledSwiftPackageManager &&
|
||||
(ios.existsSync() || macos.existsSync()) &&
|
||||
!isModule) {
|
||||
final Xcode? xcode = globals.xcode;
|
||||
final Version? xcodeVersion = xcode?.currentVersion;
|
||||
if (xcodeVersion == null || xcodeVersion.major < 15) {
|
||||
return false;
|
||||
}
|
||||
return featureFlags.isSwiftPackageManagerEnabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns a list of platform names that are supported by the project.
|
||||
List<SupportedPlatform> getSupportedPlatforms({bool includeRoot = false}) {
|
||||
final List<SupportedPlatform> platforms = includeRoot ? <SupportedPlatform>[SupportedPlatform.root] : <SupportedPlatform>[];
|
||||
|
@ -115,6 +115,46 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform {
|
||||
.childDirectory('Pods')
|
||||
.childDirectory('Target Support Files')
|
||||
.childDirectory('Pods-Runner');
|
||||
|
||||
/// 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 => hostAppRoot.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 Flutter generated directory for the Swift Package handling plugin
|
||||
/// dependencies.
|
||||
Directory get flutterPluginSwiftPackageDirectory => ephemeralDirectory
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage');
|
||||
|
||||
/// The Flutter generated Swift Package manifest (Package.swift) for plugin
|
||||
/// dependencies.
|
||||
File get flutterPluginSwiftPackageManifest =>
|
||||
flutterPluginSwiftPackageDirectory.childFile('Package.swift');
|
||||
|
||||
/// Checks if FlutterGeneratedPluginSwiftPackage has been added to the
|
||||
/// project's build settings by checking the contents of the pbxproj.
|
||||
bool get flutterPluginSwiftPackageInProjectSettings {
|
||||
return xcodeProjectInfoFile.existsSync() &&
|
||||
xcodeProjectInfoFile
|
||||
.readAsStringSync()
|
||||
.contains('FlutterGeneratedPluginSwiftPackage');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// Represents the iOS sub-project of a Flutter project.
|
||||
@ -167,16 +207,16 @@ class IosProject extends XcodeBasedProject {
|
||||
/// 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
|
||||
Directory get managedDirectory => _flutterLibRoot.childDirectory('Flutter');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig');
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('$mode.xcconfig');
|
||||
|
||||
@override
|
||||
File get generatedEnvironmentVariableExportScript => _flutterLibRoot.childDirectory('Flutter').childFile('flutter_export_environment.sh');
|
||||
File get generatedEnvironmentVariableExportScript => managedDirectory.childFile('flutter_export_environment.sh');
|
||||
|
||||
File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
||||
File get appFrameworkInfoPlist => managedDirectory.childFile('AppFrameworkInfo.plist');
|
||||
|
||||
/// The 'AppDelegate.swift' file of the host app. This file might not exist if the app project uses Objective-C.
|
||||
File get appDelegateSwift => _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift');
|
||||
@ -444,15 +484,6 @@ class IosProject extends XcodeBasedProject {
|
||||
|
||||
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) {
|
||||
@ -692,16 +723,6 @@ class MacOSProject extends XcodeBasedProject {
|
||||
@override
|
||||
Directory get hostAppRoot => 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 => hostAppRoot.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');
|
||||
|
@ -66,9 +66,11 @@ void main() {
|
||||
expect(projectUnderTest.ios.deprecatedCompiledDartFramework, isNot(exists));
|
||||
expect(projectUnderTest.ios.deprecatedProjectFlutterFramework, isNot(exists));
|
||||
expect(projectUnderTest.ios.flutterPodspec, isNot(exists));
|
||||
expect(projectUnderTest.ios.flutterPluginSwiftPackageDirectory, isNot(exists));
|
||||
|
||||
expect(projectUnderTest.linux.ephemeralDirectory, isNot(exists));
|
||||
expect(projectUnderTest.macos.ephemeralDirectory, isNot(exists));
|
||||
expect(projectUnderTest.macos.flutterPluginSwiftPackageDirectory, isNot(exists));
|
||||
expect(projectUnderTest.windows.ephemeralDirectory, isNot(exists));
|
||||
|
||||
expect(projectUnderTest.flutterPluginsFile, isNot(exists));
|
||||
@ -239,9 +241,11 @@ FlutterProject setupProjectUnderTest(Directory currentDirectory, bool setupXcode
|
||||
projectUnderTest.ios.deprecatedCompiledDartFramework.createSync(recursive: true);
|
||||
projectUnderTest.ios.deprecatedProjectFlutterFramework.createSync(recursive: true);
|
||||
projectUnderTest.ios.flutterPodspec.createSync(recursive: true);
|
||||
projectUnderTest.ios.flutterPluginSwiftPackageDirectory.createSync(recursive: true);
|
||||
|
||||
projectUnderTest.linux.ephemeralDirectory.createSync(recursive: true);
|
||||
projectUnderTest.macos.ephemeralDirectory.createSync(recursive: true);
|
||||
projectUnderTest.macos.flutterPluginSwiftPackageDirectory.createSync(recursive: true);
|
||||
projectUnderTest.windows.ephemeralDirectory.createSync(recursive: true);
|
||||
projectUnderTest.flutterPluginsFile.createSync(recursive: true);
|
||||
projectUnderTest.flutterPluginsDependenciesFile.createSync(recursive: true);
|
||||
|
@ -402,5 +402,14 @@ void main() {
|
||||
expect(nativeAssets.stable.enabledByDefault, false);
|
||||
expect(nativeAssets.stable.available, false);
|
||||
});
|
||||
|
||||
test('${swiftPackageManager.name} availability and default enabled', () {
|
||||
expect(swiftPackageManager.master.enabledByDefault, false);
|
||||
expect(swiftPackageManager.master.available, true);
|
||||
expect(swiftPackageManager.beta.enabledByDefault, false);
|
||||
expect(swiftPackageManager.beta.available, false);
|
||||
expect(swiftPackageManager.stable.enabledByDefault, false);
|
||||
expect(swiftPackageManager.stable.available, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1415,6 +1415,39 @@ name: test
|
||||
expect(flutterManifest, isNotNull);
|
||||
expect(flutterManifest!.dependencies, isEmpty);
|
||||
});
|
||||
|
||||
testWithoutContext('FlutterManifest knows if Swift Package Manager is disabled', () async {
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
disable-swift-package-manager: true
|
||||
''';
|
||||
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
|
||||
manifest,
|
||||
logger: logger,
|
||||
)!;
|
||||
|
||||
expect(flutterManifest.disabledSwiftPackageManager, true);
|
||||
});
|
||||
|
||||
testWithoutContext('FlutterManifest does not disable Swift Package Manager if missing', () async {
|
||||
const String manifest = '''
|
||||
name: test
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter:
|
||||
''';
|
||||
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
|
||||
manifest,
|
||||
logger: logger,
|
||||
)!;
|
||||
|
||||
expect(flutterManifest.disabledSwiftPackageManager, false);
|
||||
});
|
||||
}
|
||||
|
||||
Matcher matchesManifest({
|
||||
|
@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/ios/code_signing.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/ios/xcresult.dart';
|
||||
@ -20,6 +21,7 @@ import 'package:test/fake.dart';
|
||||
import 'package:unified_analytics/unified_analytics.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fake_process_manager.dart';
|
||||
import '../../src/fakes.dart';
|
||||
|
||||
@ -217,8 +219,16 @@ void main() {
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(testUsage.events, contains(
|
||||
TestUsageEvent(
|
||||
'build',
|
||||
@ -310,8 +320,16 @@ Error launching application on iPhone.''',
|
||||
buildSettings: buildSettingsWithDevTeam,
|
||||
),
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
logger.errorText,
|
||||
contains(noProvisioningProfileInstruction),
|
||||
@ -348,8 +366,16 @@ Error launching application on iPhone.''',
|
||||
buildSettings: buildSettingsWithDevTeam,
|
||||
),
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
logger.errorText,
|
||||
contains(missingPlatformInstructions('iOS 17.0')),
|
||||
@ -388,8 +414,16 @@ Could not build the precompiled application for the device.''',
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
logger.errorText,
|
||||
contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
|
||||
@ -432,10 +466,227 @@ Could not build the precompiled application for the device.''',
|
||||
])
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(logger.errorText, contains('Error (Xcode): Target aot_assembly_release failed'));
|
||||
expect(logger.errorText, isNot(contains('Building a deployable iOS app requires a selected Development Team')));
|
||||
});
|
||||
|
||||
testWithoutContext('parses redefinition of module error', () async{
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: '',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
environmentType: EnvironmentType.physical,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||
XCResultIssue.test(message: "Redefinition of module 'plugin_1_name'", subType: 'Error'),
|
||||
XCResultIssue.test(message: "Redefinition of module 'plugin_2_name'", subType: 'Error'),
|
||||
]),
|
||||
);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
usesSwiftPackageManager: true,
|
||||
);
|
||||
project.ios.podfile.createSync(recursive: true);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project,
|
||||
);
|
||||
expect(logger.errorText, contains(
|
||||
'Your project uses both CocoaPods and Swift Package Manager, which can '
|
||||
'cause the above error. It may be caused by there being both a CocoaPod '
|
||||
'and Swift Package Manager dependency for the following module(s): '
|
||||
'plugin_1_name, plugin_2_name.'
|
||||
));
|
||||
});
|
||||
|
||||
testWithoutContext('parses duplicate symbols error with arch and number', () async{
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: r'''
|
||||
duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in:
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o)
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o
|
||||
duplicate symbol '_$s29plugin_1_name23PluginNamePluginCAA15UserDefaultsApiAAWP' in:
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o)
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o
|
||||
''',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
environmentType: EnvironmentType.physical,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||
XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'),
|
||||
]),
|
||||
);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
usesSwiftPackageManager: true,
|
||||
);
|
||||
project.ios.podfile.createSync(recursive: true);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project,
|
||||
);
|
||||
expect(logger.errorText, contains(
|
||||
'Your project uses both CocoaPods and Swift Package Manager, which can '
|
||||
'cause the above error. It may be caused by there being both a CocoaPod '
|
||||
'and Swift Package Manager dependency for the following module(s): '
|
||||
'plugin_1_name.'
|
||||
));
|
||||
});
|
||||
|
||||
testWithoutContext('parses duplicate symbols error with number', () async{
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: r'''
|
||||
duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in:
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[5](PluginNamePlugin.o)
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o
|
||||
''',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
environmentType: EnvironmentType.physical,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||
XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'),
|
||||
]),
|
||||
);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
usesSwiftPackageManager: true,
|
||||
);
|
||||
project.ios.podfile.createSync(recursive: true);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project,
|
||||
);
|
||||
expect(logger.errorText, contains(
|
||||
'Your project uses both CocoaPods and Swift Package Manager, which can '
|
||||
'cause the above error. It may be caused by there being both a CocoaPod '
|
||||
'and Swift Package Manager dependency for the following module(s): '
|
||||
'plugin_1_name.'
|
||||
));
|
||||
});
|
||||
|
||||
testWithoutContext('parses duplicate symbols error without arch and number', () async{
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: r'''
|
||||
duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in:
|
||||
/Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name(PluginNamePlugin.o)
|
||||
''',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
environmentType: EnvironmentType.physical,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||
XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'),
|
||||
]),
|
||||
);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
usesSwiftPackageManager: true,
|
||||
);
|
||||
project.ios.podfile.createSync(recursive: true);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project,
|
||||
);
|
||||
expect(logger.errorText, contains(
|
||||
'Your project uses both CocoaPods and Swift Package Manager, which can '
|
||||
'cause the above error. It may be caused by there being both a CocoaPod '
|
||||
'and Swift Package Manager dependency for the following module(s): '
|
||||
'plugin_1_name.'
|
||||
));
|
||||
});
|
||||
|
||||
testUsingContext('parses missing module error', () async{
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: '',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
environmentType: EnvironmentType.physical,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||
XCResultIssue.test(message: "Module 'plugin_1_name' not found", subType: 'Error'),
|
||||
XCResultIssue.test(message: "Module 'plugin_2_name' not found", subType: 'Error'),
|
||||
]),
|
||||
);
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final FakeFlutterProject project = FakeFlutterProject(fileSystem: fs);
|
||||
project.ios.podfile.createSync(recursive: true);
|
||||
project.directory.childFile('.packages').createSync(recursive: true);
|
||||
project.manifest = FakeFlutterManifest();
|
||||
createFakePlugins(project, fs, <String>['plugin_1_name', 'plugin_2_name']);
|
||||
fs.systemTempDirectory.childFile('cache/plugin_1_name/ios/plugin_1_name/Package.swift')
|
||||
.createSync(recursive: true);
|
||||
fs.systemTempDirectory.childFile('cache/plugin_2_name/ios/plugin_2_name/Package.swift')
|
||||
.createSync(recursive: true);
|
||||
await diagnoseXcodeBuildFailure(
|
||||
buildResult,
|
||||
flutterUsage: testUsage,
|
||||
logger: logger,
|
||||
analytics: fakeAnalytics,
|
||||
fileSystem: fs,
|
||||
platform: SupportedPlatform.ios,
|
||||
project: project,
|
||||
);
|
||||
expect(logger.errorText, contains(
|
||||
'Your project uses CocoaPods as a dependency manager, but the following plugin(s) '
|
||||
'only support Swift Package Manager: plugin_1_name, plugin_2_name.'
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
group('Upgrades project.pbxproj for old asset usage', () {
|
||||
@ -454,9 +705,10 @@ Could not build the precompiled application for the device.''',
|
||||
'another line';
|
||||
|
||||
testWithoutContext('upgradePbxProjWithFlutterAssets', () async {
|
||||
final File pbxprojFile = MemoryFileSystem.test().file('project.pbxproj')
|
||||
final FakeIosProject project = FakeIosProject(fileSystem: MemoryFileSystem.test());
|
||||
final File pbxprojFile = project.xcodeProjectInfoFile
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(flutterAssetPbxProjLines);
|
||||
final FakeIosProject project = FakeIosProject(pbxprojFile);
|
||||
|
||||
bool result = upgradePbxProjWithFlutterAssets(project, logger);
|
||||
expect(result, true);
|
||||
@ -528,14 +780,88 @@ Could not build the precompiled application for the device.''',
|
||||
});
|
||||
}
|
||||
|
||||
void createFakePlugins(
|
||||
FlutterProject flutterProject,
|
||||
FileSystem fileSystem,
|
||||
List<String> pluginNames,
|
||||
) {
|
||||
const String pluginYamlTemplate = '''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
ios:
|
||||
pluginClass: PLUGIN_CLASS
|
||||
macos:
|
||||
pluginClass: PLUGIN_CLASS
|
||||
''';
|
||||
|
||||
final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
|
||||
final File packagesFile = flutterProject.directory.childFile('.packages')
|
||||
..createSync(recursive: true);
|
||||
for (final String name in pluginNames) {
|
||||
final Directory pluginDirectory = fakePubCache.childDirectory(name);
|
||||
packagesFile.writeAsStringSync(
|
||||
'$name:${pluginDirectory.childFile('lib').uri}\n',
|
||||
mode: FileMode.writeOnlyAppend);
|
||||
pluginDirectory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name));
|
||||
}
|
||||
}
|
||||
|
||||
class FakeIosProject extends Fake implements IosProject {
|
||||
FakeIosProject(this.xcodeProjectInfoFile);
|
||||
FakeIosProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
|
||||
|
||||
@override
|
||||
final File xcodeProjectInfoFile;
|
||||
Directory hostAppRoot;
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
Future<String> hostAppBundleName(BuildInfo? buildInfo) async => 'UnitTestRunner.app';
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => xcodeProjectInfoFile.fileSystem.directory('Runner.xcodeproj');
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj');
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
}
|
||||
|
||||
class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
FakeFlutterProject({
|
||||
required this.fileSystem,
|
||||
this.usesSwiftPackageManager = false,
|
||||
this.isModule = false,
|
||||
});
|
||||
|
||||
MemoryFileSystem fileSystem;
|
||||
|
||||
@override
|
||||
late final Directory directory = fileSystem.directory('app_name');
|
||||
|
||||
@override
|
||||
late FlutterManifest manifest;
|
||||
|
||||
@override
|
||||
File get flutterPluginsFile => directory.childFile('.flutter-plugins');
|
||||
|
||||
@override
|
||||
File get flutterPluginsDependenciesFile => directory.childFile('.flutter-plugins-dependencies');
|
||||
|
||||
@override
|
||||
late final IosProject ios = FakeIosProject(fileSystem: fileSystem);
|
||||
|
||||
@override
|
||||
final bool usesSwiftPackageManager;
|
||||
|
||||
@override
|
||||
final bool isModule;
|
||||
}
|
||||
|
||||
class FakeFlutterManifest extends Fake implements FlutterManifest {
|
||||
@override
|
||||
Set<String> get dependencies => <String>{};
|
||||
}
|
||||
|
@ -0,0 +1,555 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapod_utils.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
group('processPodsIfNeeded', () {
|
||||
late MemoryFileSystem fs;
|
||||
late FakeCocoaPods cocoaPods;
|
||||
late BufferLogger logger;
|
||||
|
||||
// Adds basic properties to the flutterProject and its subprojects.
|
||||
void setUpProject(FakeFlutterProject flutterProject, MemoryFileSystem fileSystem) {
|
||||
flutterProject
|
||||
..manifest = FakeFlutterManifest()
|
||||
..directory = fileSystem.systemTempDirectory.childDirectory('app')
|
||||
..flutterPluginsFile = flutterProject.directory.childFile('.flutter-plugins')
|
||||
..flutterPluginsDependenciesFile = flutterProject.directory.childFile('.flutter-plugins-dependencies')
|
||||
..ios = FakeIosProject(fileSystem: fileSystem, parent: flutterProject)
|
||||
..macos = FakeMacOSProject(fileSystem: fileSystem, parent: flutterProject)
|
||||
..android = FakeAndroidProject()
|
||||
..web = FakeWebProject()
|
||||
..windows = FakeWindowsProject()
|
||||
..linux = FakeLinuxProject();
|
||||
flutterProject.directory.childFile('.packages').createSync(recursive: true);
|
||||
}
|
||||
|
||||
setUp(() async {
|
||||
fs = MemoryFileSystem.test();
|
||||
cocoaPods = FakeCocoaPods();
|
||||
logger = BufferLogger.test();
|
||||
});
|
||||
|
||||
void createFakePlugins(
|
||||
FlutterProject flutterProject,
|
||||
FileSystem fileSystem,
|
||||
List<String> pluginNames,
|
||||
) {
|
||||
const String pluginYamlTemplate = '''
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
ios:
|
||||
pluginClass: PLUGIN_CLASS
|
||||
macos:
|
||||
pluginClass: PLUGIN_CLASS
|
||||
''';
|
||||
|
||||
final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
|
||||
final File packagesFile = flutterProject.directory.childFile('.packages')
|
||||
..createSync(recursive: true);
|
||||
for (final String name in pluginNames) {
|
||||
final Directory pluginDirectory = fakePubCache.childDirectory(name);
|
||||
packagesFile.writeAsStringSync(
|
||||
'$name:${pluginDirectory.childFile('lib').uri}\n',
|
||||
mode: FileMode.writeOnlyAppend);
|
||||
pluginDirectory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name));
|
||||
}
|
||||
}
|
||||
|
||||
group('for iOS', () {
|
||||
group('using CocoaPods only', () {
|
||||
testUsingContext('processes when there are plugins', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('processes when no plugins but the project is a module and podfile exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
flutterProject.isModule = true;
|
||||
flutterProject.ios.podfile.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext("skips when no plugins and the project is a module but podfile doesn't exist", () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
flutterProject.isModule = true;
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('skips when no plugins and project is not a module', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
});
|
||||
|
||||
group('using Swift Package Manager', () {
|
||||
testUsingContext('processes if podfile exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
flutterProject.ios.podfile.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('skip if podfile does not exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('process if podfile does not exists but forceCocoaPodsOnly is true', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
flutterProject.ios.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.ios,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
forceCocoaPodsOnly: true,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
expect(
|
||||
logger.warningText,
|
||||
'Swift Package Manager does not yet support this command. '
|
||||
'CocoaPods will be used instead.\n');
|
||||
expect(
|
||||
flutterProject.ios.flutterPluginSwiftPackageManifest.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
Logger: () => logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('for macOS', () {
|
||||
group('using CocoaPods only', () {
|
||||
testUsingContext('processes when there are plugins', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('processes when no plugins but the project is a module and podfile exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
flutterProject.isModule = true;
|
||||
flutterProject.macos.podfile.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext("skips when no plugins and the project is a module but podfile doesn't exist", () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
flutterProject.isModule = true;
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('skips when no plugins and project is not a module', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
});
|
||||
|
||||
group('using Swift Package Manager', () {
|
||||
testUsingContext('processes if podfile exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
flutterProject.macos.podfile.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('skip if podfile does not exists', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('process if podfile does not exists but forceCocoaPodsOnly is true', () async {
|
||||
final FakeFlutterProject flutterProject = FakeFlutterProject();
|
||||
setUpProject(flutterProject, fs);
|
||||
createFakePlugins(flutterProject, fs, <String>[
|
||||
'plugin_one',
|
||||
'plugin_two'
|
||||
]);
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
flutterProject.macos.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
|
||||
await processPodsIfNeeded(
|
||||
flutterProject.macos,
|
||||
fs.currentDirectory.childDirectory('build').path,
|
||||
BuildMode.debug,
|
||||
forceCocoaPodsOnly: true,
|
||||
);
|
||||
expect(cocoaPods.processedPods, isTrue);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
expect(
|
||||
logger.warningText,
|
||||
'Swift Package Manager does not yet support this command. '
|
||||
'CocoaPods will be used instead.\n');
|
||||
expect(
|
||||
flutterProject.macos.flutterPluginSwiftPackageManifest.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
CocoaPods: () => cocoaPods,
|
||||
Logger: () => logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeFlutterManifest extends Fake implements FlutterManifest {
|
||||
@override
|
||||
Set<String> get dependencies => <String>{};
|
||||
}
|
||||
|
||||
class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
@override
|
||||
bool isModule = false;
|
||||
|
||||
@override
|
||||
bool usesSwiftPackageManager = false;
|
||||
|
||||
@override
|
||||
late FlutterManifest manifest;
|
||||
|
||||
@override
|
||||
late Directory directory;
|
||||
|
||||
@override
|
||||
late File flutterPluginsFile;
|
||||
|
||||
@override
|
||||
late File flutterPluginsDependenciesFile;
|
||||
|
||||
@override
|
||||
late IosProject ios;
|
||||
|
||||
@override
|
||||
late MacOSProject macos;
|
||||
|
||||
@override
|
||||
late AndroidProject android;
|
||||
|
||||
@override
|
||||
late WebProject web;
|
||||
|
||||
@override
|
||||
late LinuxProject linux;
|
||||
|
||||
@override
|
||||
late WindowsProject windows;
|
||||
}
|
||||
|
||||
class FakeMacOSProject extends Fake implements MacOSProject {
|
||||
FakeMacOSProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
required this.parent,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
|
||||
|
||||
@override
|
||||
String pluginConfigKey = 'macos';
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
Directory hostAppRoot;
|
||||
|
||||
bool exists = true;
|
||||
|
||||
@override
|
||||
bool existsSync() => exists;
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => hostAppRoot
|
||||
.childDirectory('Runner.xcodeproj')
|
||||
.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
File get flutterPluginSwiftPackageManifest => hostAppRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.childFile('Package.swift');
|
||||
}
|
||||
|
||||
class FakeIosProject extends Fake implements IosProject {
|
||||
FakeIosProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
required this.parent,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
|
||||
|
||||
@override
|
||||
String pluginConfigKey = 'ios';
|
||||
|
||||
@override
|
||||
final FlutterProject parent;
|
||||
|
||||
@override
|
||||
Directory hostAppRoot;
|
||||
|
||||
@override
|
||||
bool exists = true;
|
||||
|
||||
@override
|
||||
bool existsSync() => exists;
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => hostAppRoot
|
||||
.childDirectory('Runner.xcodeproj')
|
||||
.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
File get flutterPluginSwiftPackageManifest => hostAppRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.childFile('Package.swift');
|
||||
}
|
||||
|
||||
class FakeAndroidProject extends Fake implements AndroidProject {
|
||||
@override
|
||||
String pluginConfigKey = 'android';
|
||||
|
||||
@override
|
||||
bool existsSync() => false;
|
||||
}
|
||||
|
||||
class FakeWebProject extends Fake implements WebProject {
|
||||
@override
|
||||
String pluginConfigKey = 'web';
|
||||
|
||||
@override
|
||||
bool existsSync() => false;
|
||||
}
|
||||
|
||||
class FakeWindowsProject extends Fake implements WindowsProject {
|
||||
@override
|
||||
String pluginConfigKey = 'windows';
|
||||
|
||||
@override
|
||||
bool existsSync() => false;
|
||||
}
|
||||
|
||||
class FakeLinuxProject extends Fake implements LinuxProject {
|
||||
@override
|
||||
String pluginConfigKey = 'linux';
|
||||
|
||||
@override
|
||||
bool existsSync() => false;
|
||||
}
|
||||
|
||||
class FakeCocoaPods extends Fake implements CocoaPods {
|
||||
bool podfileSetup = false;
|
||||
bool processedPods = false;
|
||||
|
||||
@override
|
||||
Future<bool> processPods({
|
||||
required XcodeBasedProject xcodeProject,
|
||||
required BuildMode buildMode,
|
||||
bool dependenciesChanged = true,
|
||||
}) async {
|
||||
processedPods = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setupPodfile(XcodeBasedProject xcodeProject) async {
|
||||
podfileSetup = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {}
|
||||
}
|
@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/version.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/features.dart';
|
||||
import 'package:flutter_tools/src/flutter_plugins.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
||||
@ -457,6 +458,19 @@ void main() {
|
||||
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||
});
|
||||
|
||||
testUsingContext("doesn't throw, if using Swift Package Manager and Podfile is missing.", () async {
|
||||
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
||||
final bool didInstall = await cocoaPodsUnderTest.processPods(
|
||||
xcodeProject: projectUnderTest.ios,
|
||||
buildMode: BuildMode.debug,
|
||||
);
|
||||
expect(didInstall, isFalse);
|
||||
expect(fakeProcessManager, hasNoRemainingExpectations);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('throws, if specs repo is outdated.', () async {
|
||||
final FlutterProject projectUnderTest = setupProjectUnderTest();
|
||||
pretendPodIsInstalled();
|
||||
@ -1380,7 +1394,11 @@ Specs satisfying the `$fakePluginName (from `Flutter/ephemeral/.symlinks/plugins
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({this.isInstalled = true, this.buildSettings = const <String, String>{}});
|
||||
FakeXcodeProjectInterpreter({
|
||||
this.isInstalled = true,
|
||||
this.buildSettings = const <String, String>{},
|
||||
this.version,
|
||||
});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
@ -1393,4 +1411,7 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete
|
||||
}) async => buildSettings;
|
||||
|
||||
final Map<String, String> buildSettings;
|
||||
|
||||
@override
|
||||
Version? version;
|
||||
}
|
||||
|
@ -0,0 +1,692 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
||||
import 'package:flutter_tools/src/macos/darwin_dependency_management.dart';
|
||||
import 'package:flutter_tools/src/macos/swift_package_manager.dart';
|
||||
import 'package:flutter_tools/src/platform_plugins.dart';
|
||||
import 'package:flutter_tools/src/plugins.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
||||
void main() {
|
||||
const List<SupportedPlatform> supportedPlatforms = <SupportedPlatform>[
|
||||
SupportedPlatform.ios,
|
||||
SupportedPlatform.macos,
|
||||
];
|
||||
|
||||
group('DarwinDependencyManagement', () {
|
||||
for (final SupportedPlatform platform in supportedPlatforms) {
|
||||
group('for ${platform.name}', () {
|
||||
group('generatePluginsSwiftPackage', () {
|
||||
testWithoutContext('throw if invalid platform', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(fileSystem: fs),
|
||||
plugins: <Plugin>[],
|
||||
cocoapods: FakeCocoaPods(),
|
||||
swiftPackageManager: FakeSwiftPackageManager(),
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
|
||||
await expectLater(() => dependencyManagement.setUp(
|
||||
platform: SupportedPlatform.android,
|
||||
),
|
||||
throwsToolExit(
|
||||
message: 'The platform android is incompatible with Darwin Dependency Managers. Only iOS and macOS are allowed.',
|
||||
),
|
||||
);
|
||||
});
|
||||
group('when using Swift Package Manager', () {
|
||||
testWithoutContext('with only CocoaPod plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'cocoapod_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginPodspecPath: cocoapodPluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
});
|
||||
|
||||
testWithoutContext('with only Swift Package Manager plugins and no pod integration', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('with only Swift Package Manager plugins but project not migrated', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true);
|
||||
projectPodfile.writeAsStringSync('Standard Podfile template');
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods(
|
||||
podFile: projectPodfile,
|
||||
);
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos;
|
||||
xcodeProject.podfile.createSync(recursive: true);
|
||||
xcodeProject.podfile.writeAsStringSync('Standard Podfile template');
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: project,
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('with only Swift Package Manager plugins with preexisting standard CocoaPods Podfile', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true);
|
||||
projectPodfile.writeAsStringSync('Standard Podfile template');
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods(
|
||||
podFile: projectPodfile,
|
||||
);
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos;
|
||||
xcodeProject.podfile.createSync(recursive: true);
|
||||
xcodeProject.podfile.writeAsStringSync('Standard Podfile template');
|
||||
xcodeProject.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
xcodeProject.xcodeProjectInfoFile.writeAsStringSync('FlutterGeneratedPluginSwiftPackage');
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: project,
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
final String xcconfigPrefix = platform == SupportedPlatform.macos ? 'Flutter-' : '';
|
||||
expect(testLogger.warningText, contains(
|
||||
'All plugins found for ${platform.name} are Swift Packages, '
|
||||
'but your project still has CocoaPods integration. To remove '
|
||||
'CocoaPods integration, complete the following steps:\n'
|
||||
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
|
||||
' * Also in the ${platform.name}/ directory, delete the Podfile\n'
|
||||
' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" '
|
||||
'in your ${platform.name}/Flutter/${xcconfigPrefix}Debug.xcconfig\n'
|
||||
' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" '
|
||||
'in your ${platform.name}/Flutter/${xcconfigPrefix}Release.xcconfig\n\n'
|
||||
"Removing CocoaPods integration will improve the project's build time.\n"
|
||||
));
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('with only Swift Package Manager plugins with preexisting custom CocoaPods Podfile', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true);
|
||||
projectPodfile.writeAsStringSync('Standard Podfile template');
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods(
|
||||
podFile: projectPodfile,
|
||||
);
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos;
|
||||
xcodeProject.podfile.createSync(recursive: true);
|
||||
xcodeProject.podfile.writeAsStringSync('Non-Standard Podfile template');
|
||||
xcodeProject.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
xcodeProject.xcodeProjectInfoFile.writeAsStringSync('FlutterGeneratedPluginSwiftPackage');
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: project,
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
final String xcconfigPrefix = platform == SupportedPlatform.macos ? 'Flutter-' : '';
|
||||
expect(testLogger.warningText, contains(
|
||||
'All plugins found for ${platform.name} are Swift Packages, '
|
||||
'but your project still has CocoaPods integration. Your '
|
||||
'project uses a non-standard Podfile and will need to be '
|
||||
'migrated to Swift Package Manager manually. Some steps you '
|
||||
'may need to complete include:\n'
|
||||
' * In the ${platform.name}/ directory run "pod deintegrate"\n'
|
||||
' * Transition any Pod dependencies to Swift Package equivalents. '
|
||||
'See https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app\n'
|
||||
' * Transition any custom logic\n'
|
||||
' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" '
|
||||
'in your ${platform.name}/Flutter/${xcconfigPrefix}Debug.xcconfig\n'
|
||||
' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" '
|
||||
'in your ${platform.name}/Flutter/${xcconfigPrefix}Release.xcconfig\n\n'
|
||||
"Removing CocoaPods integration will improve the project's build time.\n"
|
||||
));
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('with mixed plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec')
|
||||
..createSync(recursive: true);
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'cocoapod_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginPodspecPath: cocoapodPluginPodspec.path,
|
||||
),
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
FakePlugin(
|
||||
name: 'neither_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('when not using Swift Package Manager', () {
|
||||
testWithoutContext('but project already migrated', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'cocoapod_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginPodspecPath: cocoapodPluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
final FakeFlutterProject project = FakeFlutterProject(
|
||||
usesSwiftPackageManager: true,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos;
|
||||
xcodeProject.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
xcodeProject.xcodeProjectInfoFile.writeAsStringSync(
|
||||
'FlutterGeneratedPluginSwiftPackage',
|
||||
);
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: project,
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isTrue);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
});
|
||||
|
||||
testWithoutContext('with only CocoaPod plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'cocoapod_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginPodspecPath: cocoapodPluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isFalse);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isTrue);
|
||||
});
|
||||
|
||||
testWithoutContext('with only Swift Package Manager plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'swift_package_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await expectLater(() => dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
),
|
||||
throwsToolExit(
|
||||
message: 'Plugin swift_package_plugin_1 is only Swift Package Manager compatible. Try '
|
||||
'enabling Swift Package Manager by running '
|
||||
'"flutter config --enable-swift-package-manager" or remove the '
|
||||
'plugin as a dependency.',
|
||||
),
|
||||
);
|
||||
expect(swiftPackageManager.generated, isFalse);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('when project is a module', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final BufferLogger testLogger = BufferLogger.test();
|
||||
final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec')
|
||||
..createSync(recursive: true);
|
||||
final List<Plugin> plugins = <Plugin>[
|
||||
FakePlugin(
|
||||
name: 'cocoapod_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginPodspecPath: cocoapodPluginPodspec.path,
|
||||
),
|
||||
];
|
||||
final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager(
|
||||
expectedPlugins: plugins,
|
||||
);
|
||||
final FakeCocoaPods cocoaPods = FakeCocoaPods();
|
||||
|
||||
final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement(
|
||||
project: FakeFlutterProject(
|
||||
fileSystem: fs,
|
||||
isModule: true,
|
||||
),
|
||||
plugins: plugins,
|
||||
cocoapods: cocoaPods,
|
||||
swiftPackageManager: swiftPackageManager,
|
||||
fileSystem: fs,
|
||||
logger: testLogger,
|
||||
);
|
||||
await dependencyManagement.setUp(
|
||||
platform: platform,
|
||||
);
|
||||
expect(swiftPackageManager.generated, isFalse);
|
||||
expect(testLogger.warningText, isEmpty);
|
||||
expect(testLogger.statusText, isEmpty);
|
||||
expect(cocoaPods.podfileSetup, isFalse);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class FakeIosProject extends Fake implements IosProject {
|
||||
FakeIosProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
|
||||
|
||||
@override
|
||||
Directory hostAppRoot;
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => hostAppRoot.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
bool get flutterPluginSwiftPackageInProjectSettings {
|
||||
return xcodeProjectInfoFile.existsSync() &&
|
||||
xcodeProjectInfoFile
|
||||
.readAsStringSync()
|
||||
.contains('FlutterGeneratedPluginSwiftPackage');
|
||||
}
|
||||
|
||||
@override
|
||||
Directory get managedDirectory => hostAppRoot.childDirectory('Flutter');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('$mode.xcconfig');
|
||||
}
|
||||
|
||||
class FakeMacOSProject extends Fake implements MacOSProject {
|
||||
FakeMacOSProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory('macos');
|
||||
|
||||
@override
|
||||
Directory hostAppRoot;
|
||||
|
||||
@override
|
||||
File get podfile => hostAppRoot.childFile('Podfile');
|
||||
|
||||
@override
|
||||
File get podfileLock => hostAppRoot.childFile('Podfile.lock');
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
bool get flutterPluginSwiftPackageInProjectSettings {
|
||||
return xcodeProjectInfoFile.existsSync() &&
|
||||
xcodeProjectInfoFile
|
||||
.readAsStringSync()
|
||||
.contains('FlutterGeneratedPluginSwiftPackage');
|
||||
}
|
||||
|
||||
@override
|
||||
Directory get managedDirectory => hostAppRoot.childDirectory('Flutter');
|
||||
|
||||
@override
|
||||
File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig');
|
||||
}
|
||||
|
||||
class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
FakeFlutterProject({
|
||||
required this.fileSystem,
|
||||
this.usesSwiftPackageManager = false,
|
||||
this.isModule = false,
|
||||
});
|
||||
|
||||
MemoryFileSystem fileSystem;
|
||||
|
||||
|
||||
@override
|
||||
late final IosProject ios = FakeIosProject(fileSystem: fileSystem);
|
||||
|
||||
@override
|
||||
late final MacOSProject macos = FakeMacOSProject(fileSystem: fileSystem);
|
||||
|
||||
@override
|
||||
final bool usesSwiftPackageManager;
|
||||
|
||||
@override
|
||||
final bool isModule;
|
||||
}
|
||||
|
||||
class FakeSwiftPackageManager extends Fake implements SwiftPackageManager {
|
||||
FakeSwiftPackageManager({
|
||||
this.expectedPlugins,
|
||||
});
|
||||
|
||||
bool generated = false;
|
||||
final List<Plugin>? expectedPlugins;
|
||||
|
||||
@override
|
||||
Future<void> generatePluginsSwiftPackage(
|
||||
List<Plugin> plugins,
|
||||
SupportedPlatform platform,
|
||||
XcodeBasedProject project,
|
||||
) async {
|
||||
generated = true;
|
||||
expect(plugins, expectedPlugins);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeCocoaPods extends Fake implements CocoaPods {
|
||||
FakeCocoaPods({
|
||||
this.podFile,
|
||||
this.configIncludesPods = true,
|
||||
});
|
||||
|
||||
File? podFile;
|
||||
|
||||
bool podfileSetup = false;
|
||||
bool addedPodDependencyToFlutterXcconfig = false;
|
||||
bool configIncludesPods;
|
||||
|
||||
@override
|
||||
Future<void> setupPodfile(XcodeBasedProject xcodeProject) async {
|
||||
podfileSetup = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject) {
|
||||
addedPodDependencyToFlutterXcconfig = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<File> getPodfileTemplate(XcodeBasedProject xcodeProject, Directory runnerProject) async {
|
||||
return podFile!;
|
||||
}
|
||||
|
||||
@override
|
||||
bool xcconfigIncludesPods(File xcodeConfig) {
|
||||
return configIncludesPods;
|
||||
}
|
||||
|
||||
@override
|
||||
String includePodsXcconfig(String mode) {
|
||||
return 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode
|
||||
.toLowerCase()}.xcconfig';
|
||||
}
|
||||
}
|
||||
|
||||
class FakePlugin extends Fake implements Plugin {
|
||||
FakePlugin({
|
||||
required this.name,
|
||||
required this.platforms,
|
||||
String? pluginSwiftPackageManifestPath,
|
||||
String? pluginPodspecPath,
|
||||
}) : _pluginSwiftPackageManifestPath = pluginSwiftPackageManifestPath,
|
||||
_pluginPodspecPath = pluginPodspecPath;
|
||||
|
||||
final String? _pluginSwiftPackageManifestPath;
|
||||
|
||||
final String? _pluginPodspecPath;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
final Map<String, PluginPlatform> platforms;
|
||||
|
||||
@override
|
||||
String? pluginSwiftPackageManifestPath(
|
||||
FileSystem fileSystem,
|
||||
String platform,
|
||||
) {
|
||||
return _pluginSwiftPackageManifestPath;
|
||||
}
|
||||
|
||||
@override
|
||||
String? pluginPodspecPath(
|
||||
FileSystem fileSystem,
|
||||
String platform,
|
||||
) {
|
||||
return _pluginPodspecPath;
|
||||
}
|
||||
}
|
||||
|
||||
class FakePluginPlatform extends Fake implements PluginPlatform {}
|
@ -0,0 +1,424 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/isolated/mustache_template.dart';
|
||||
import 'package:flutter_tools/src/macos/swift_package_manager.dart';
|
||||
import 'package:flutter_tools/src/platform_plugins.dart';
|
||||
import 'package:flutter_tools/src/plugins.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
||||
const String _doubleIndent = ' ';
|
||||
|
||||
void main() {
|
||||
const List<SupportedPlatform> supportedPlatforms = <SupportedPlatform>[
|
||||
SupportedPlatform.ios,
|
||||
SupportedPlatform.macos,
|
||||
];
|
||||
|
||||
group('SwiftPackageManager', () {
|
||||
for (final SupportedPlatform platform in supportedPlatforms) {
|
||||
group('for ${platform.name}', () {
|
||||
|
||||
group('generatePluginsSwiftPackage', () {
|
||||
testWithoutContext('throw if invalid platform', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
|
||||
final SwiftPackageManager spm = SwiftPackageManager(
|
||||
fileSystem: fs,
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
|
||||
await expectLater(() => spm.generatePluginsSwiftPackage(
|
||||
<Plugin>[],
|
||||
SupportedPlatform.android,
|
||||
project,
|
||||
),
|
||||
throwsToolExit(message: 'The platform android is not compatible with Swift Package Manager. Only iOS and macOS are allowed.'),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('skip if no dependencies and not already migrated', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
|
||||
final SwiftPackageManager spm = SwiftPackageManager(
|
||||
fileSystem: fs,
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
await spm.generatePluginsSwiftPackage(
|
||||
<Plugin>[],
|
||||
platform,
|
||||
project,
|
||||
);
|
||||
|
||||
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('generate if no dependencies and already migrated', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
project.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
project.xcodeProjectInfoFile.writeAsStringSync('''
|
||||
' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };';
|
||||
''');
|
||||
|
||||
final SwiftPackageManager spm = SwiftPackageManager(
|
||||
fileSystem: fs,
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
await spm.generatePluginsSwiftPackage(
|
||||
<Plugin>[],
|
||||
platform,
|
||||
project,
|
||||
);
|
||||
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
|
||||
expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
platforms: [
|
||||
$supportedPlatform
|
||||
],
|
||||
products: [
|
||||
.library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
|
||||
],
|
||||
dependencies: [
|
||||
$_doubleIndent
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "FlutterGeneratedPluginSwiftPackage"
|
||||
)
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
|
||||
testWithoutContext('generate with single dependency', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
|
||||
final File validPlugin1Manifest = fs.file('/local/path/to/plugins/valid_plugin_1/Package.swift')..createSync(recursive: true);
|
||||
final FakePlugin validPlugin1 = FakePlugin(
|
||||
name: 'valid_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: validPlugin1Manifest.path,
|
||||
);
|
||||
final SwiftPackageManager spm = SwiftPackageManager(
|
||||
fileSystem: fs,
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
await spm.generatePluginsSwiftPackage(
|
||||
<Plugin>[validPlugin1],
|
||||
platform,
|
||||
project,
|
||||
);
|
||||
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
|
||||
expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
platforms: [
|
||||
$supportedPlatform
|
||||
],
|
||||
products: [
|
||||
.library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "valid_plugin_1", path: "/local/path/to/plugins/valid_plugin_1")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
dependencies: [
|
||||
.product(name: "valid-plugin-1", package: "valid_plugin_1")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
|
||||
testWithoutContext('generate with multiple dependencies', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final FakePlugin nonPlatformCompatiblePlugin = FakePlugin(
|
||||
name: 'invalid_plugin_due_to_incompatible_platform',
|
||||
platforms: <String, PluginPlatform>{},
|
||||
pluginSwiftPackageManifestPath: '/some/path',
|
||||
);
|
||||
final FakePlugin pluginSwiftPackageManifestIsNull = FakePlugin(
|
||||
name: 'invalid_plugin_due_to_null_plugin_swift_package_path',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: null,
|
||||
);
|
||||
final FakePlugin pluginSwiftPackageManifestNotExists = FakePlugin(
|
||||
name: 'invalid_plugin_due_to_plugin_swift_package_path_does_not_exist',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: '/some/path',
|
||||
);
|
||||
|
||||
final File validPlugin1Manifest = fs.file('/local/path/to/plugins/valid_plugin_1/Package.swift')..createSync(recursive: true);
|
||||
final FakePlugin validPlugin1 = FakePlugin(
|
||||
name: 'valid_plugin_1',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: validPlugin1Manifest.path,
|
||||
);
|
||||
final File validPlugin2Manifest = fs.file('/.pub-cache/plugins/valid_plugin_2/Package.swift')..createSync(recursive: true);
|
||||
final FakePlugin validPlugin2 = FakePlugin(
|
||||
name: 'valid_plugin_2',
|
||||
platforms: <String, PluginPlatform>{platform.name: FakePluginPlatform()},
|
||||
pluginSwiftPackageManifestPath: validPlugin2Manifest.path,
|
||||
);
|
||||
|
||||
final SwiftPackageManager spm = SwiftPackageManager(
|
||||
fileSystem: fs,
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
await spm.generatePluginsSwiftPackage(
|
||||
<Plugin>[
|
||||
nonPlatformCompatiblePlugin,
|
||||
pluginSwiftPackageManifestIsNull,
|
||||
pluginSwiftPackageManifestNotExists,
|
||||
validPlugin1,
|
||||
validPlugin2,
|
||||
],
|
||||
platform,
|
||||
project,
|
||||
);
|
||||
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios
|
||||
? '.iOS("12.0")'
|
||||
: '.macOS("10.14")';
|
||||
expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue);
|
||||
expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
platforms: [
|
||||
$supportedPlatform
|
||||
],
|
||||
products: [
|
||||
.library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "valid_plugin_1", path: "/local/path/to/plugins/valid_plugin_1"),
|
||||
.package(name: "valid_plugin_2", path: "/.pub-cache/plugins/valid_plugin_2")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
dependencies: [
|
||||
.product(name: "valid-plugin-1", package: "valid_plugin_1"),
|
||||
.product(name: "valid-plugin-2", package: "valid_plugin_2")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
});
|
||||
|
||||
group('updateMinimumDeployment', () {
|
||||
testWithoutContext('return if invalid deploymentTarget', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
project.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform);
|
||||
SwiftPackageManager.updateMinimumDeployment(
|
||||
project: project,
|
||||
platform: platform,
|
||||
deploymentTarget: '',
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageManifest.readAsLinesSync(),
|
||||
contains(supportedPlatform),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('return if deploymentTarget is lower than default', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
project.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform);
|
||||
SwiftPackageManager.updateMinimumDeployment(
|
||||
project: project,
|
||||
platform: platform,
|
||||
deploymentTarget: '9.0',
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageManifest.readAsLinesSync(),
|
||||
contains(supportedPlatform),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('return if deploymentTarget is same than default', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
project.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform);
|
||||
SwiftPackageManager.updateMinimumDeployment(
|
||||
project: project,
|
||||
platform: platform,
|
||||
deploymentTarget: platform == SupportedPlatform.ios ? '12.0' : '10.14',
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageManifest.readAsLinesSync(),
|
||||
contains(supportedPlatform),
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('update if deploymentTarget is higher than default', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final FakeXcodeProject project = FakeXcodeProject(
|
||||
platform: platform.name,
|
||||
fileSystem: fs,
|
||||
);
|
||||
final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")';
|
||||
project.flutterPluginSwiftPackageManifest.createSync(recursive: true);
|
||||
project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform);
|
||||
SwiftPackageManager.updateMinimumDeployment(
|
||||
project: project,
|
||||
platform: platform,
|
||||
deploymentTarget: '14.0',
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageManifest
|
||||
.readAsLinesSync()
|
||||
.contains(supportedPlatform),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageManifest.readAsLinesSync(),
|
||||
contains(platform == SupportedPlatform.ios ? '.iOS("14.0")' : '.macOS("14.0")'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class FakeXcodeProject extends Fake implements IosProject {
|
||||
FakeXcodeProject({
|
||||
required MemoryFileSystem fileSystem,
|
||||
required String platform,
|
||||
}) : hostAppRoot = fileSystem.directory('app_name').childDirectory(platform);
|
||||
|
||||
@override
|
||||
Directory hostAppRoot;
|
||||
|
||||
@override
|
||||
Directory get xcodeProject => hostAppRoot.childDirectory('$hostAppProjectName.xcodeproj');
|
||||
|
||||
@override
|
||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||
|
||||
@override
|
||||
String hostAppProjectName = 'Runner';
|
||||
|
||||
@override
|
||||
Directory get flutterPluginSwiftPackageDirectory => hostAppRoot
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage');
|
||||
|
||||
@override
|
||||
File get flutterPluginSwiftPackageManifest =>
|
||||
flutterPluginSwiftPackageDirectory.childFile('Package.swift');
|
||||
|
||||
@override
|
||||
bool get flutterPluginSwiftPackageInProjectSettings {
|
||||
return xcodeProjectInfoFile.existsSync() &&
|
||||
xcodeProjectInfoFile
|
||||
.readAsStringSync()
|
||||
.contains('FlutterGeneratedPluginSwiftPackage');
|
||||
}
|
||||
}
|
||||
|
||||
class FakePlugin extends Fake implements Plugin {
|
||||
FakePlugin({
|
||||
required this.name,
|
||||
required this.platforms,
|
||||
required String? pluginSwiftPackageManifestPath,
|
||||
}) : _pluginSwiftPackageManifestPath = pluginSwiftPackageManifestPath;
|
||||
|
||||
final String? _pluginSwiftPackageManifestPath;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
final Map<String, PluginPlatform> platforms;
|
||||
|
||||
@override
|
||||
String? pluginSwiftPackageManifestPath(
|
||||
FileSystem fileSystem,
|
||||
String platform,
|
||||
) {
|
||||
return _pluginSwiftPackageManifestPath;
|
||||
}
|
||||
}
|
||||
|
||||
class FakePluginPlatform extends Fake implements PluginPlatform {}
|
@ -0,0 +1,375 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/version.dart';
|
||||
import 'package:flutter_tools/src/isolated/mustache_template.dart';
|
||||
import 'package:flutter_tools/src/macos/swift_packages.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
||||
const String _doubleIndent = ' ';
|
||||
|
||||
void main() {
|
||||
group('SwiftPackage', () {
|
||||
testWithoutContext('createSwiftPackage also creates source file for each default target', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
const String target1Name = 'Target1';
|
||||
const String target2Name = 'Target2';
|
||||
final File target1SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift');
|
||||
final File target2SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift');
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[],
|
||||
products: <SwiftPackageProduct>[],
|
||||
dependencies: <SwiftPackagePackageDependency>[],
|
||||
targets: <SwiftPackageTarget>[
|
||||
SwiftPackageTarget.defaultTarget(name: target1Name),
|
||||
SwiftPackageTarget.defaultTarget(name: 'Target2'),
|
||||
],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.existsSync(), isTrue);
|
||||
expect(target1SourceFile.existsSync(), isTrue);
|
||||
expect(target2SourceFile.existsSync(), isTrue);
|
||||
});
|
||||
|
||||
testWithoutContext('createSwiftPackage also creates source file for binary target', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[],
|
||||
products: <SwiftPackageProduct>[],
|
||||
dependencies: <SwiftPackagePackageDependency>[],
|
||||
targets: <SwiftPackageTarget>[
|
||||
SwiftPackageTarget.binaryTarget(name: 'BinaryTarget', relativePath: ''),
|
||||
],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.existsSync(), isTrue);
|
||||
expect(fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/BinaryTarget/BinaryTarget.swift').existsSync(), isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('createSwiftPackage does not creates source file if already exists', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
const String target1Name = 'Target1';
|
||||
const String target2Name = 'Target2';
|
||||
final File target1SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift');
|
||||
final File target2SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift');
|
||||
|
||||
fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/SomeSourceFile.swift').createSync(recursive: true);
|
||||
fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/SomeSourceFile.swift').createSync(recursive: true);
|
||||
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[],
|
||||
products: <SwiftPackageProduct>[],
|
||||
dependencies: <SwiftPackagePackageDependency>[],
|
||||
targets: <SwiftPackageTarget>[
|
||||
SwiftPackageTarget.defaultTarget(name: target1Name),
|
||||
SwiftPackageTarget.defaultTarget(name: 'Target2'),
|
||||
],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.existsSync(), isTrue);
|
||||
expect(target1SourceFile.existsSync(), isFalse);
|
||||
expect(target2SourceFile.existsSync(), isFalse);
|
||||
});
|
||||
|
||||
group('create Package.swift from template', () {
|
||||
testWithoutContext('with none in each field', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[],
|
||||
products: <SwiftPackageProduct>[],
|
||||
dependencies: <SwiftPackagePackageDependency>[],
|
||||
targets: <SwiftPackageTarget>[],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
products: [
|
||||
$_doubleIndent
|
||||
],
|
||||
dependencies: [
|
||||
$_doubleIndent
|
||||
],
|
||||
targets: [
|
||||
$_doubleIndent
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
|
||||
testWithoutContext('with single in each field', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[
|
||||
SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.ios, version: Version(12, 0, null)),
|
||||
],
|
||||
products: <SwiftPackageProduct>[
|
||||
SwiftPackageProduct(name: 'Product1', targets: <String>['Target1']),
|
||||
],
|
||||
dependencies: <SwiftPackagePackageDependency>[
|
||||
SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'),
|
||||
],
|
||||
targets: <SwiftPackageTarget>[
|
||||
SwiftPackageTarget.defaultTarget(
|
||||
name: 'Target1',
|
||||
dependencies: <SwiftPackageTargetDependency>[
|
||||
SwiftPackageTargetDependency.product(name: 'TargetDependency1', packageName: 'TargetDependency1Package'),
|
||||
],
|
||||
)
|
||||
],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
platforms: [
|
||||
.iOS("12.0")
|
||||
],
|
||||
products: [
|
||||
.library(name: "Product1", targets: ["Target1"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Dependency1", path: "/path/to/dependency1")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Target1",
|
||||
dependencies: [
|
||||
.product(name: "TargetDependency1", package: "TargetDependency1Package")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
|
||||
testWithoutContext('with multiple in each field', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem();
|
||||
final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift');
|
||||
final SwiftPackage swiftPackage = SwiftPackage(
|
||||
manifest: swiftPackageFile,
|
||||
name: 'FlutterGeneratedPluginSwiftPackage',
|
||||
platforms: <SwiftPackageSupportedPlatform>[
|
||||
SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.ios, version: Version(12, 0, null)),
|
||||
SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.macos, version: Version(10, 14, null)),
|
||||
],
|
||||
products: <SwiftPackageProduct>[
|
||||
SwiftPackageProduct(name: 'Product1', targets: <String>['Target1']),
|
||||
SwiftPackageProduct(name: 'Product2', targets: <String>['Target2'])
|
||||
],
|
||||
dependencies: <SwiftPackagePackageDependency>[
|
||||
SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'),
|
||||
SwiftPackagePackageDependency(name: 'Dependency2', path: '/path/to/dependency2'),
|
||||
],
|
||||
targets: <SwiftPackageTarget>[
|
||||
SwiftPackageTarget.binaryTarget(name: 'Target1', relativePath: '/path/to/target1'),
|
||||
SwiftPackageTarget.defaultTarget(
|
||||
name: 'Target2',
|
||||
dependencies: <SwiftPackageTargetDependency>[
|
||||
SwiftPackageTargetDependency.target(name: 'TargetDependency1'),
|
||||
SwiftPackageTargetDependency.product(name: 'TargetDependency2', packageName: 'TargetDependency2Package'),
|
||||
],
|
||||
)
|
||||
],
|
||||
templateRenderer: const MustacheTemplateRenderer(),
|
||||
);
|
||||
swiftPackage.createSwiftPackage();
|
||||
expect(swiftPackageFile.readAsStringSync(), '''
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FlutterGeneratedPluginSwiftPackage",
|
||||
platforms: [
|
||||
.iOS("12.0"),
|
||||
.macOS("10.14")
|
||||
],
|
||||
products: [
|
||||
.library(name: "Product1", targets: ["Target1"]),
|
||||
.library(name: "Product2", targets: ["Target2"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Dependency1", path: "/path/to/dependency1"),
|
||||
.package(name: "Dependency2", path: "/path/to/dependency2")
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(
|
||||
name: "Target1",
|
||||
path: "/path/to/target1"
|
||||
),
|
||||
.target(
|
||||
name: "Target2",
|
||||
dependencies: [
|
||||
.target(name: "TargetDependency1"),
|
||||
.product(name: "TargetDependency2", package: "TargetDependency2Package")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
''');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('Format SwiftPackageSupportedPlatform', () {
|
||||
final SwiftPackageSupportedPlatform supportedPlatform = SwiftPackageSupportedPlatform(
|
||||
platform: SwiftPackagePlatform.ios,
|
||||
version: Version(17, 0, null),
|
||||
);
|
||||
expect(supportedPlatform.format(), '.iOS("17.0")');
|
||||
});
|
||||
|
||||
group('Format SwiftPackageProduct', () {
|
||||
testWithoutContext('without targets and libraryType', () {
|
||||
final SwiftPackageProduct product = SwiftPackageProduct(
|
||||
name: 'ProductName',
|
||||
targets: <String>[],
|
||||
);
|
||||
expect(product.format(), '.library(name: "ProductName")');
|
||||
});
|
||||
|
||||
testWithoutContext('with targets', () {
|
||||
final SwiftPackageProduct singleProduct = SwiftPackageProduct(
|
||||
name: 'ProductName',
|
||||
targets: <String>['Target1'],
|
||||
);
|
||||
expect(singleProduct.format(), '.library(name: "ProductName", targets: ["Target1"])');
|
||||
|
||||
final SwiftPackageProduct multipleProducts = SwiftPackageProduct(
|
||||
name: 'ProductName',
|
||||
targets: <String>['Target1', 'Target2'],
|
||||
);
|
||||
expect(multipleProducts.format(), '.library(name: "ProductName", targets: ["Target1", "Target2"])');
|
||||
});
|
||||
|
||||
testWithoutContext('with libraryType', () {
|
||||
final SwiftPackageProduct product = SwiftPackageProduct(
|
||||
name: 'ProductName',
|
||||
targets: <String>[],
|
||||
libraryType: SwiftPackageLibraryType.dynamic,
|
||||
);
|
||||
expect(product.format(), '.library(name: "ProductName", type: .dynamic)');
|
||||
});
|
||||
|
||||
testWithoutContext('with targets and libraryType', () {
|
||||
final SwiftPackageProduct product = SwiftPackageProduct(
|
||||
name: 'ProductName',
|
||||
targets: <String>['Target1', 'Target2'],
|
||||
libraryType: SwiftPackageLibraryType.dynamic,
|
||||
);
|
||||
expect(product.format(), '.library(name: "ProductName", type: .dynamic, targets: ["Target1", "Target2"])');
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('Format SwiftPackagePackageDependency', () {
|
||||
final SwiftPackagePackageDependency supportedPlatform = SwiftPackagePackageDependency(
|
||||
name: 'DependencyName',
|
||||
path: '/path/to/dependency',
|
||||
);
|
||||
expect(supportedPlatform.format(), '.package(name: "DependencyName", path: "/path/to/dependency")');
|
||||
});
|
||||
|
||||
group('Format SwiftPackageTarget', () {
|
||||
testWithoutContext('as default target with multiple SwiftPackageTargetDependency', () {
|
||||
final SwiftPackageTarget product = SwiftPackageTarget.defaultTarget(
|
||||
name: 'ProductName',
|
||||
dependencies: <SwiftPackageTargetDependency>[
|
||||
SwiftPackageTargetDependency.target(name: 'Dependency1'),
|
||||
SwiftPackageTargetDependency.product(name: 'Dependency2', packageName: 'Dependency2Package'),
|
||||
],
|
||||
);
|
||||
expect(product.format(), '''
|
||||
.target(
|
||||
name: "ProductName",
|
||||
dependencies: [
|
||||
.target(name: "Dependency1"),
|
||||
.product(name: "Dependency2", package: "Dependency2Package")
|
||||
]
|
||||
)''');
|
||||
});
|
||||
|
||||
testWithoutContext('as default target with no SwiftPackageTargetDependency', () {
|
||||
final SwiftPackageTarget product = SwiftPackageTarget.defaultTarget(
|
||||
name: 'ProductName',
|
||||
);
|
||||
expect(product.format(), '''
|
||||
.target(
|
||||
name: "ProductName"
|
||||
)''');
|
||||
});
|
||||
|
||||
testWithoutContext('as binaryTarget', () {
|
||||
final SwiftPackageTarget product = SwiftPackageTarget.binaryTarget(
|
||||
name: 'ProductName',
|
||||
relativePath: '/path/to/target',
|
||||
);
|
||||
expect(product.format(), '''
|
||||
.binaryTarget(
|
||||
name: "ProductName",
|
||||
path: "/path/to/target"
|
||||
)''');
|
||||
});
|
||||
});
|
||||
|
||||
group('Format SwiftPackageTargetDependency', () {
|
||||
testWithoutContext('with only name', () {
|
||||
final SwiftPackageTargetDependency targetDependency = SwiftPackageTargetDependency.target(
|
||||
name: 'DependencyName',
|
||||
);
|
||||
expect(targetDependency.format(), ' .target(name: "DependencyName")');
|
||||
});
|
||||
|
||||
testWithoutContext('with name and package', () {
|
||||
final SwiftPackageTargetDependency targetDependency = SwiftPackageTargetDependency.product(
|
||||
name: 'DependencyName',
|
||||
packageName: 'PackageName',
|
||||
);
|
||||
expect(targetDependency.format(), ' .product(name: "DependencyName", package: "PackageName")');
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,8 @@ import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/flutter_plugins.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/macos/darwin_dependency_management.dart';
|
||||
import 'package:flutter_tools/src/platform_plugins.dart';
|
||||
import 'package:flutter_tools/src/plugins.dart';
|
||||
import 'package:flutter_tools/src/preview_device.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
@ -522,6 +524,7 @@ dependencies:
|
||||
expect(jsonContent['dependencyGraph'], expectedDependencyGraph);
|
||||
expect(jsonContent['date_created'], dateCreated.toString());
|
||||
expect(jsonContent['version'], '1.0.0');
|
||||
expect(jsonContent['swift_package_manager_enabled'], false);
|
||||
|
||||
// Make sure tests are updated if a new object is added/removed.
|
||||
final List<String> expectedKeys = <String>[
|
||||
@ -530,6 +533,7 @@ dependencies:
|
||||
'dependencyGraph',
|
||||
'date_created',
|
||||
'version',
|
||||
'swift_package_manager_enabled',
|
||||
];
|
||||
expect(jsonContent.keys, expectedKeys);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -609,6 +613,79 @@ dependencies:
|
||||
FlutterVersion: () => flutterVersion,
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'.flutter-plugins-dependencies contains swift_package_manager_enabled true when project is using Swift Package Manager', () async {
|
||||
createPlugin(
|
||||
name: 'plugin-a',
|
||||
platforms: const <String, _PluginPlatformInfo>{
|
||||
// Native-only; should include native build.
|
||||
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
||||
// Hybrid native and Dart; should include native build.
|
||||
'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar', sharedDarwinSource: true),
|
||||
// Web; should not have the native build key at all since it doesn't apply.
|
||||
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
||||
// Dart-only; should not include native build.
|
||||
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
||||
});
|
||||
iosProject.testExists = true;
|
||||
|
||||
final DateTime dateCreated = DateTime(1970);
|
||||
systemClock.currentTime = dateCreated;
|
||||
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
|
||||
await refreshPluginsList(flutterProject);
|
||||
|
||||
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
|
||||
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
||||
.readAsStringSync();
|
||||
final Map<String, dynamic> jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
||||
|
||||
expect(jsonContent['swift_package_manager_enabled'], true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
SystemClock: () => systemClock,
|
||||
FlutterVersion: () => flutterVersion,
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'.flutter-plugins-dependencies contains swift_package_manager_enabled false when project is using Swift Package Manager but forceCocoaPodsOnly is true',
|
||||
() async {
|
||||
createPlugin(
|
||||
name: 'plugin-a',
|
||||
platforms: const <String, _PluginPlatformInfo>{
|
||||
// Native-only; should include native build.
|
||||
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
||||
// Hybrid native and Dart; should include native build.
|
||||
'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar', sharedDarwinSource: true),
|
||||
// Web; should not have the native build key at all since it doesn't apply.
|
||||
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
||||
// Dart-only; should not include native build.
|
||||
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
||||
});
|
||||
iosProject.testExists = true;
|
||||
|
||||
final DateTime dateCreated = DateTime(1970);
|
||||
systemClock.currentTime = dateCreated;
|
||||
|
||||
flutterProject.usesSwiftPackageManager = true;
|
||||
|
||||
await refreshPluginsList(flutterProject, forceCocoaPodsOnly: true);
|
||||
|
||||
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
|
||||
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
||||
.readAsStringSync();
|
||||
final Map<String, dynamic> jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
||||
|
||||
expect(jsonContent['swift_package_manager_enabled'], false);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
SystemClock: () => systemClock,
|
||||
FlutterVersion: () => flutterVersion,
|
||||
});
|
||||
|
||||
testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async {
|
||||
simulatePodInstallRun(iosProject);
|
||||
simulatePodInstallRun(macosProject);
|
||||
@ -886,8 +963,12 @@ flutter:
|
||||
ios:
|
||||
dartPluginClass: SomePlugin
|
||||
''');
|
||||
|
||||
await injectPlugins(flutterProject, iosPlatform: true);
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
iosPlatform: true,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
|
||||
final File registrantFile = iosProject.pluginRegistrantImplementation;
|
||||
|
||||
@ -909,8 +990,12 @@ flutter:
|
||||
macos:
|
||||
dartPluginClass: SomePlugin
|
||||
''');
|
||||
|
||||
await injectPlugins(flutterProject, macOSPlatform: true);
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
macOSPlatform: true,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
|
||||
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
||||
|
||||
@ -933,8 +1018,12 @@ flutter:
|
||||
pluginClass: none
|
||||
dartPluginClass: SomePlugin
|
||||
''');
|
||||
|
||||
await injectPlugins(flutterProject, macOSPlatform: true);
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
macOSPlatform: true,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
|
||||
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
||||
|
||||
@ -953,8 +1042,12 @@ flutter:
|
||||
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync(r'''
|
||||
"aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\"
|
||||
''');
|
||||
|
||||
await injectPlugins(flutterProject, macOSPlatform: true);
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
macOSPlatform: true,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
|
||||
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
|
||||
|
||||
@ -1194,6 +1287,35 @@ The Flutter Preview device does not support the following plugins from your pubs
|
||||
FileSystem: () => fsWindows,
|
||||
ProcessManager: () => FakeProcessManager.empty(),
|
||||
});
|
||||
|
||||
testUsingContext('iOS and macOS project setup up Darwin Dependency Management', () async {
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
iosPlatform: true,
|
||||
macOSPlatform: true,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
expect(
|
||||
dependencyManagement.setupPlatforms,
|
||||
<SupportedPlatform>[SupportedPlatform.ios, SupportedPlatform.macos],
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('non-iOS or macOS project does not setup up Darwin Dependency Management', () async {
|
||||
final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement();
|
||||
await injectPlugins(
|
||||
flutterProject,
|
||||
darwinDependencyManagement: dependencyManagement,
|
||||
);
|
||||
expect(dependencyManagement.setupPlatforms, <SupportedPlatform>[]);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
});
|
||||
|
||||
group('createPluginSymlinks', () {
|
||||
@ -1390,6 +1512,154 @@ The Flutter Preview device does not support the following plugins from your pubs
|
||||
|
||||
});
|
||||
|
||||
group('Plugin files', () {
|
||||
testWithoutContext('pluginSwiftPackageManifestPath for iOS and macOS plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: ''),
|
||||
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test'),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey),
|
||||
'/path/to/test/ios/test/Package.swift',
|
||||
);
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey),
|
||||
'/path/to/test/macos/test/Package.swift',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('pluginSwiftPackageManifestPath for darwin plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: '', sharedDarwinSource: true),
|
||||
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test', sharedDarwinSource: true),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey),
|
||||
'/path/to/test/darwin/test/Package.swift',
|
||||
);
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey),
|
||||
'/path/to/test/darwin/test/Package.swift',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('pluginSwiftPackageManifestPath for non darwin plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: ''),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
plugin.pluginSwiftPackageManifestPath(fs, WindowsPlugin.kConfigKey),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('pluginPodspecPath for iOS and macOS plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: ''),
|
||||
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test'),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(
|
||||
plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey),
|
||||
'/path/to/test/ios/test.podspec',
|
||||
);
|
||||
expect(
|
||||
plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey),
|
||||
'/path/to/test/macos/test.podspec',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('pluginPodspecPath for darwin plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: '', sharedDarwinSource: true),
|
||||
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test', sharedDarwinSource: true),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(
|
||||
plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey),
|
||||
'/path/to/test/darwin/test.podspec',
|
||||
);
|
||||
expect(
|
||||
plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey),
|
||||
'/path/to/test/darwin/test.podspec',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('pluginPodspecPath for non darwin plugins', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Plugin plugin = Plugin(
|
||||
name: 'test',
|
||||
path: '/path/to/test/',
|
||||
defaultPackagePlatforms: const <String, String>{},
|
||||
pluginDartClassPlatforms: const <String, String>{},
|
||||
platforms: const <String, PluginPlatform>{
|
||||
WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: ''),
|
||||
},
|
||||
dependencies: <String>[],
|
||||
isDirectDependency: true,
|
||||
);
|
||||
|
||||
expect(plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey), isNull);
|
||||
expect(plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey), isNull);
|
||||
expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull);
|
||||
});
|
||||
});
|
||||
testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async {
|
||||
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
||||
final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]');
|
||||
@ -1498,6 +1768,9 @@ class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
@override
|
||||
bool isModule = false;
|
||||
|
||||
@override
|
||||
bool usesSwiftPackageManager = false;
|
||||
|
||||
@override
|
||||
late FlutterManifest manifest;
|
||||
|
||||
@ -1684,3 +1957,14 @@ class FakeSystemClock extends Fake implements SystemClock {
|
||||
return currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDarwinDependencyManagement extends Fake implements DarwinDependencyManagement {
|
||||
List<SupportedPlatform> setupPlatforms = <SupportedPlatform>[];
|
||||
|
||||
@override
|
||||
Future<void> setUp({
|
||||
required SupportedPlatform platform,
|
||||
}) async {
|
||||
setupPlatforms.add(platform);
|
||||
}
|
||||
}
|
||||
|
@ -375,6 +375,91 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('usesSwiftPackageManager', () {
|
||||
testUsingContext('is true when iOS project exists', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('ios').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest();
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('is true when macOS project exists', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('macos').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest();
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('is false when disabled via manifest', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('ios').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest(disabledSwiftPackageManager: true);
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext("is false when iOS and macOS project don't exist", () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
final FlutterManifest manifest = FakeFlutterManifest();
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('is false when Xcode is less than 15', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('ios').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest();
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(14, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('is false when Swift Package Manager feature is not enabled', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('ios').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest();
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
|
||||
testUsingContext('is false when project is a module', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final Directory projectDirectory = fs.directory('path');
|
||||
projectDirectory.childDirectory('ios').createSync(recursive: true);
|
||||
final FlutterManifest manifest = FakeFlutterManifest(isModule: true);
|
||||
final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest);
|
||||
expect(project.usesSwiftPackageManager, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true),
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)),
|
||||
});
|
||||
});
|
||||
|
||||
group('java gradle agp compatibility', () {
|
||||
Future<FlutterProject?> configureGradleAgpForTest({
|
||||
required String gradleV,
|
||||
@ -1046,6 +1131,31 @@ plugins {
|
||||
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
||||
});
|
||||
});
|
||||
|
||||
group('flutterSwiftPackageInProjectSettings', () {
|
||||
testWithMocks('is false if pbxproj missing', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
expect(project.ios.xcodeProjectInfoFile.existsSync(), isFalse);
|
||||
expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse);
|
||||
});
|
||||
|
||||
testWithMocks('is false if pbxproj does not contain FlutterGeneratedPluginSwiftPackage in build process', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
project.ios.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue);
|
||||
expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse);
|
||||
});
|
||||
|
||||
testWithMocks('is true if pbxproj does contain FlutterGeneratedPluginSwiftPackage in build process', () async {
|
||||
final FlutterProject project = await someProject();
|
||||
project.ios.xcodeProjectInfoFile.createSync(recursive: true);
|
||||
project.ios.xcodeProjectInfoFile.writeAsStringSync('''
|
||||
' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };';
|
||||
''');
|
||||
expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue);
|
||||
expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isTrue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('application bundle name', () {
|
||||
@ -1724,6 +1834,10 @@ File androidPluginRegistrant(Directory parent) {
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({
|
||||
this.version,
|
||||
});
|
||||
|
||||
final Map<XcodeProjectBuildContext, Map<String, String>> buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
|
||||
late XcodeProjectInfo xcodeProjectInfo;
|
||||
|
||||
@ -1745,6 +1859,9 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete
|
||||
|
||||
@override
|
||||
bool get isInstalled => true;
|
||||
|
||||
@override
|
||||
Version? version;
|
||||
}
|
||||
|
||||
class FakeAndroidSdkWithDir extends Fake implements AndroidSdk {
|
||||
@ -1755,3 +1872,16 @@ class FakeAndroidSdkWithDir extends Fake implements AndroidSdk {
|
||||
@override
|
||||
Directory get directory => _directory;
|
||||
}
|
||||
|
||||
class FakeFlutterManifest extends Fake implements FlutterManifest {
|
||||
FakeFlutterManifest({
|
||||
this.disabledSwiftPackageManager = false,
|
||||
this.isModule = false,
|
||||
});
|
||||
|
||||
@override
|
||||
bool disabledSwiftPackageManager;
|
||||
|
||||
@override
|
||||
bool isModule;
|
||||
}
|
||||
|
@ -223,6 +223,179 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('prepare', () {
|
||||
test('exits with useful error message when build mode not set', () {
|
||||
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||
..createSync(recursive: true);
|
||||
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||
..createSync(recursive: true);
|
||||
final File pipe = fileSystem.file('/tmp/pipe')
|
||||
..createSync(recursive: true);
|
||||
const String buildMode = 'Debug';
|
||||
final TestContext context = TestContext(
|
||||
<String>['prepare'],
|
||||
<String, String>{
|
||||
'ACTION': 'build',
|
||||
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||
'FLUTTER_ROOT': flutterRoot.path,
|
||||
'INFOPLIST_PATH': 'Info.plist',
|
||||
},
|
||||
commands: <FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'${flutterRoot.path}/bin/flutter',
|
||||
'assemble',
|
||||
'--no-version-check',
|
||||
'--output=${buildDir.path}/',
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dIosArchs=',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
'-dTreeShakeIcons=',
|
||||
'-dTrackWidgetCreation=',
|
||||
'-dDartObfuscation=',
|
||||
'-dAction=build',
|
||||
'-dFrontendServerStarterPath=',
|
||||
'--ExtraGenSnapshotOptions=',
|
||||
'--DartDefines=',
|
||||
'--ExtraFrontEndOptions=',
|
||||
'debug_unpack_ios',
|
||||
],
|
||||
),
|
||||
],
|
||||
fileSystem: fileSystem,
|
||||
scriptOutputStreamFile: pipe,
|
||||
);
|
||||
expect(
|
||||
() => context.run(),
|
||||
throwsException,
|
||||
);
|
||||
expect(
|
||||
context.stderr,
|
||||
contains('ERROR: Unknown FLUTTER_BUILD_MODE: null.\n'),
|
||||
);
|
||||
});
|
||||
test('calls flutter assemble', () {
|
||||
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||
..createSync(recursive: true);
|
||||
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||
..createSync(recursive: true);
|
||||
final File pipe = fileSystem.file('/tmp/pipe')
|
||||
..createSync(recursive: true);
|
||||
const String buildMode = 'Debug';
|
||||
final TestContext context = TestContext(
|
||||
<String>['prepare'],
|
||||
<String, String>{
|
||||
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||
'CONFIGURATION': buildMode,
|
||||
'FLUTTER_ROOT': flutterRoot.path,
|
||||
'INFOPLIST_PATH': 'Info.plist',
|
||||
},
|
||||
commands: <FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'${flutterRoot.path}/bin/flutter',
|
||||
'assemble',
|
||||
'--no-version-check',
|
||||
'--output=${buildDir.path}/',
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dIosArchs=',
|
||||
'-dSdkRoot=',
|
||||
'-dSplitDebugInfo=',
|
||||
'-dTreeShakeIcons=',
|
||||
'-dTrackWidgetCreation=',
|
||||
'-dDartObfuscation=',
|
||||
'-dAction=',
|
||||
'-dFrontendServerStarterPath=',
|
||||
'--ExtraGenSnapshotOptions=',
|
||||
'--DartDefines=',
|
||||
'--ExtraFrontEndOptions=',
|
||||
'debug_unpack_ios',
|
||||
],
|
||||
),
|
||||
],
|
||||
fileSystem: fileSystem,
|
||||
scriptOutputStreamFile: pipe,
|
||||
)..run();
|
||||
expect(context.stderr, isEmpty);
|
||||
});
|
||||
|
||||
test('forwards all env variables to flutter assemble', () {
|
||||
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||
..createSync(recursive: true);
|
||||
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||
..createSync(recursive: true);
|
||||
const String archs = 'arm64';
|
||||
const String buildMode = 'Release';
|
||||
const String dartObfuscation = 'false';
|
||||
const String dartDefines = 'flutter.inspector.structuredErrors%3Dtrue';
|
||||
const String expandedCodeSignIdentity = 'F1326572E0B71C3C8442805230CB4B33B708A2E2';
|
||||
const String extraFrontEndOptions = '--some-option';
|
||||
const String extraGenSnapshotOptions = '--obfuscate';
|
||||
const String frontendServerStarterPath = '/path/to/frontend_server_starter.dart';
|
||||
const String sdkRoot = '/path/to/sdk';
|
||||
const String splitDebugInfo = '/path/to/split/debug/info';
|
||||
const String trackWidgetCreation = 'true';
|
||||
const String treeShake = 'true';
|
||||
final TestContext context = TestContext(
|
||||
<String>['prepare'],
|
||||
<String, String>{
|
||||
'ACTION': 'install',
|
||||
'ARCHS': archs,
|
||||
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||
'CODE_SIGNING_REQUIRED': 'YES',
|
||||
'CONFIGURATION': buildMode,
|
||||
'DART_DEFINES': dartDefines,
|
||||
'DART_OBFUSCATION': dartObfuscation,
|
||||
'EXPANDED_CODE_SIGN_IDENTITY': expandedCodeSignIdentity,
|
||||
'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions,
|
||||
'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions,
|
||||
'FLUTTER_ROOT': flutterRoot.path,
|
||||
'FRONTEND_SERVER_STARTER_PATH': frontendServerStarterPath,
|
||||
'INFOPLIST_PATH': 'Info.plist',
|
||||
'SDKROOT': sdkRoot,
|
||||
'FLAVOR': 'strawberry',
|
||||
'SPLIT_DEBUG_INFO': splitDebugInfo,
|
||||
'TRACK_WIDGET_CREATION': trackWidgetCreation,
|
||||
'TREE_SHAKE_ICONS': treeShake,
|
||||
},
|
||||
commands: <FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'${flutterRoot.path}/bin/flutter',
|
||||
'assemble',
|
||||
'--no-version-check',
|
||||
'--output=${buildDir.path}/',
|
||||
'-dTargetPlatform=ios',
|
||||
'-dTargetFile=lib/main.dart',
|
||||
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||
'-dFlavor=strawberry',
|
||||
'-dIosArchs=$archs',
|
||||
'-dSdkRoot=$sdkRoot',
|
||||
'-dSplitDebugInfo=$splitDebugInfo',
|
||||
'-dTreeShakeIcons=$treeShake',
|
||||
'-dTrackWidgetCreation=$trackWidgetCreation',
|
||||
'-dDartObfuscation=$dartObfuscation',
|
||||
'-dAction=install',
|
||||
'-dFrontendServerStarterPath=$frontendServerStarterPath',
|
||||
'--ExtraGenSnapshotOptions=$extraGenSnapshotOptions',
|
||||
'--DartDefines=$dartDefines',
|
||||
'--ExtraFrontEndOptions=$extraFrontEndOptions',
|
||||
'-dCodesignIdentity=$expandedCodeSignIdentity',
|
||||
'release_unpack_ios',
|
||||
],
|
||||
),
|
||||
],
|
||||
fileSystem: fileSystem,
|
||||
)..run();
|
||||
expect(context.stderr, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class TestContext extends Context {
|
||||
|
@ -0,0 +1,213 @@
|
||||
// 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:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
|
||||
void main() {
|
||||
group('IosProject', () {
|
||||
testWithoutContext('managedDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(project.managedDirectory.path, 'app_name/ios/Flutter');
|
||||
});
|
||||
|
||||
testWithoutContext('module managedDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs, isModule: true),
|
||||
);
|
||||
expect(project.managedDirectory.path, 'app_name/.ios/Flutter');
|
||||
});
|
||||
|
||||
testWithoutContext('ephemeralDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(project.ephemeralDirectory.path, 'app_name/ios/Flutter/ephemeral');
|
||||
});
|
||||
|
||||
testWithoutContext('module ephemeralDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs, isModule: true),
|
||||
);
|
||||
expect(project.ephemeralDirectory.path, 'app_name/.ios/Flutter/ephemeral');
|
||||
});
|
||||
|
||||
testWithoutContext('flutterPluginSwiftPackageDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageDirectory.path,
|
||||
'app_name/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('module flutterPluginSwiftPackageDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs, isModule: true),
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageDirectory.path,
|
||||
'app_name/.ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('xcodeConfigFor', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
project.xcodeConfigFor('Debug').path,
|
||||
'app_name/ios/Flutter/Debug.xcconfig',
|
||||
);
|
||||
});
|
||||
|
||||
group('projectInfo', () {
|
||||
testUsingContext('is null if XcodeProjectInterpreter is null', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.projectInfo(), isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => null,
|
||||
});
|
||||
|
||||
testUsingContext('is null if XcodeProjectInterpreter is not installed', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.projectInfo(), isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(
|
||||
isInstalled: false,
|
||||
),
|
||||
});
|
||||
|
||||
testUsingContext('is null if xcodeproj does not exist', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(await project.projectInfo(), isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
|
||||
});
|
||||
|
||||
testUsingContext('returns XcodeProjectInfo', () async {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final IosProject project = IosProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
project.xcodeProject.createSync(recursive: true);
|
||||
expect(await project.projectInfo(), isNotNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('MacOSProject', () {
|
||||
testWithoutContext('managedDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final MacOSProject project = MacOSProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(project.managedDirectory.path, 'app_name/macos/Flutter');
|
||||
});
|
||||
|
||||
testWithoutContext('module managedDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final MacOSProject project = MacOSProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(project.managedDirectory.path, 'app_name/macos/Flutter');
|
||||
});
|
||||
|
||||
testWithoutContext('ephemeralDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final MacOSProject project = MacOSProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(project.ephemeralDirectory.path, 'app_name/macos/Flutter/ephemeral');
|
||||
});
|
||||
|
||||
testWithoutContext('flutterPluginSwiftPackageDirectory', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final MacOSProject project = MacOSProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
project.flutterPluginSwiftPackageDirectory.path,
|
||||
'app_name/macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage',
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('xcodeConfigFor', () {
|
||||
final MemoryFileSystem fs = MemoryFileSystem.test();
|
||||
final MacOSProject project = MacOSProject.fromFlutter(
|
||||
FakeFlutterProject(fileSystem: fs),
|
||||
);
|
||||
expect(
|
||||
project.xcodeConfigFor('Debug').path,
|
||||
'app_name/macos/Flutter/Flutter-Debug.xcconfig',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeFlutterProject extends Fake implements FlutterProject {
|
||||
FakeFlutterProject({
|
||||
required this.fileSystem,
|
||||
this.isModule = false,
|
||||
});
|
||||
|
||||
MemoryFileSystem fileSystem;
|
||||
|
||||
@override
|
||||
late final Directory directory = fileSystem.directory('app_name');
|
||||
|
||||
@override
|
||||
bool isModule = false;
|
||||
}
|
||||
|
||||
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
||||
FakeXcodeProjectInterpreter({
|
||||
this.isInstalled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
final bool isInstalled;
|
||||
|
||||
@override
|
||||
Future<XcodeProjectInfo?> getInfo(String projectPath, {String? projectFilename}) async {
|
||||
return XcodeProjectInfo(
|
||||
<String>[],
|
||||
<String>[],
|
||||
<String>['Runner'],
|
||||
BufferLogger.test(),
|
||||
);
|
||||
}
|
||||
}
|
@ -213,4 +213,80 @@ void main() {
|
||||
expect(logger.statusText, isEmpty);
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.plistJsonContent can parse pbxproj file', () async {
|
||||
final String xcodeProjectFile = fileSystem.path.join(
|
||||
getFlutterRoot(),
|
||||
'dev',
|
||||
'integration_tests',
|
||||
'flutter_gallery',
|
||||
'ios',
|
||||
'Runner.xcodeproj',
|
||||
'project.pbxproj'
|
||||
);
|
||||
|
||||
final BufferLogger logger = BufferLogger(
|
||||
terminal: Terminal.test(),
|
||||
outputPreferences: OutputPreferences(),
|
||||
);
|
||||
|
||||
final PlistParser parser = PlistParser(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final String? projectFileAsJson = parser.plistJsonContent(xcodeProjectFile);
|
||||
expect(projectFileAsJson, isNotNull);
|
||||
expect(projectFileAsJson, contains('"PRODUCT_NAME":"Flutter Gallery"'));
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.plistJsonContent can parse pbxproj file with unicode and emojis', () async {
|
||||
String xcodeProjectFile = fileSystem.path.join(
|
||||
getFlutterRoot(),
|
||||
'dev',
|
||||
'integration_tests',
|
||||
'flutter_gallery',
|
||||
'ios',
|
||||
'Runner.xcodeproj',
|
||||
'project.pbxproj'
|
||||
);
|
||||
|
||||
final BufferLogger logger = BufferLogger(
|
||||
terminal: Terminal.test(),
|
||||
outputPreferences: OutputPreferences(),
|
||||
);
|
||||
|
||||
final PlistParser parser = PlistParser(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
xcodeProjectFile = xcodeProjectFile.replaceAll('AppDelegate.m', 'AppDélegate.m');
|
||||
xcodeProjectFile = xcodeProjectFile.replaceAll('AppDelegate.h', 'App👍Delegate.h');
|
||||
|
||||
final String? projectFileAsJson = parser.plistJsonContent(xcodeProjectFile);
|
||||
expect(projectFileAsJson, isNotNull);
|
||||
expect(projectFileAsJson, contains('"PRODUCT_NAME":"Flutter Gallery"'));
|
||||
expect(logger.errorText, isEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
|
||||
testWithoutContext('PlistParser.plistJsonContent returns null when errors', () async {
|
||||
final BufferLogger logger = BufferLogger(
|
||||
terminal: Terminal.test(),
|
||||
outputPreferences: OutputPreferences(),
|
||||
);
|
||||
|
||||
final PlistParser parser = PlistParser(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
|
||||
final String? projectFileAsJson = parser.plistJsonContent('bad/path');
|
||||
expect(projectFileAsJson, isNull);
|
||||
expect(logger.errorText, isNotEmpty);
|
||||
}, skip: !platform.isMacOS); // [intended] requires macos tool chain.
|
||||
}
|
||||
|
@ -0,0 +1,770 @@
|
||||
// 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 'dart:convert';
|
||||
|
||||
import 'package:flutter_tools/src/base/error_handling_io.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import 'test_utils.dart';
|
||||
|
||||
void main() {
|
||||
final String flutterBin = fileSystem.path.join(
|
||||
getFlutterRoot(),
|
||||
'bin',
|
||||
'flutter',
|
||||
);
|
||||
|
||||
final List<String> platforms = <String>['ios', 'macos'];
|
||||
for (final String platformName in platforms) {
|
||||
final List<String> iosLanguages = <String>[
|
||||
if (platformName == 'ios') 'objc',
|
||||
'swift',
|
||||
];
|
||||
final _Plugin integrationTestPlugin = _integrationTestPlugin(platformName);
|
||||
|
||||
for (final String iosLanguage in iosLanguages) {
|
||||
test('Swift Package Manager not used when feature is disabled for $platformName with $iosLanguage', () async {
|
||||
final Directory workingDirectory = fileSystem.systemTempDirectory
|
||||
.createTempSync('swift_package_manager_disabled.');
|
||||
final String workingDirectoryPath = workingDirectory.path;
|
||||
try {
|
||||
await _disableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
|
||||
// Create and build an app using the CocoaPods version of
|
||||
// integration_test.
|
||||
final String appDirectoryPath = await _createApp(
|
||||
flutterBin,
|
||||
workingDirectoryPath,
|
||||
iosLanguage: iosLanguage,
|
||||
platform: platformName,
|
||||
options: <String>['--platforms=$platformName'],
|
||||
);
|
||||
_addDependency(
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
plugin: integrationTestPlugin,
|
||||
);
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--debug', '-v'],
|
||||
expectedLines: _expectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
unexpectedLines: _unexpectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childFile('Podfile')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
} finally {
|
||||
ErrorHandlingFileSystem.deleteIfExists(
|
||||
workingDirectory,
|
||||
recursive: true,
|
||||
);
|
||||
}
|
||||
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
|
||||
|
||||
test('Swift Package Manager integration for $platformName with $iosLanguage', () async {
|
||||
final Directory workingDirectory = fileSystem.systemTempDirectory
|
||||
.createTempSync('swift_package_manager_enabled.');
|
||||
final String workingDirectoryPath = workingDirectory.path;
|
||||
try {
|
||||
// Create and build an app using the Swift Package Manager version of
|
||||
// integration_test.
|
||||
await _enableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
|
||||
final String appDirectoryPath = await _createApp(
|
||||
flutterBin,
|
||||
workingDirectoryPath,
|
||||
iosLanguage: iosLanguage,
|
||||
platform: platformName,
|
||||
usesSwiftPackageManager: true,
|
||||
options: <String>['--platforms=$platformName'],
|
||||
);
|
||||
_addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin);
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--debug', '-v'],
|
||||
expectedLines: _expectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
swiftPackageMangerEnabled: true,
|
||||
swiftPackagePlugin: integrationTestPlugin,
|
||||
),
|
||||
unexpectedLines: _unexpectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
swiftPackageMangerEnabled: true,
|
||||
swiftPackagePlugin: integrationTestPlugin,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childFile('Podfile')
|
||||
.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
// Build an app using both a CocoaPods and Swift Package Manager plugin.
|
||||
await _cleanApp(flutterBin, appDirectoryPath);
|
||||
final _Plugin createdCocoaPodsPlugin = await _createPlugin(
|
||||
flutterBin,
|
||||
workingDirectoryPath,
|
||||
platform: platformName,
|
||||
iosLanguage: iosLanguage,
|
||||
);
|
||||
_addDependency(
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
plugin: createdCocoaPodsPlugin,
|
||||
);
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--debug', '-v'],
|
||||
expectedLines: _expectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: createdCocoaPodsPlugin,
|
||||
swiftPackageMangerEnabled: true,
|
||||
swiftPackagePlugin: integrationTestPlugin,
|
||||
),
|
||||
unexpectedLines: _unexpectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: createdCocoaPodsPlugin,
|
||||
swiftPackageMangerEnabled: true,
|
||||
swiftPackagePlugin: integrationTestPlugin,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childFile('Podfile')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
// Build app again but with Swift Package Manager disabled by config.
|
||||
// App will now use CocoaPods version of integration_test plugin.
|
||||
await _disableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
await _cleanApp(flutterBin, appDirectoryPath);
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--debug', '-v'],
|
||||
expectedLines: _expectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
unexpectedLines: _unexpectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
);
|
||||
|
||||
// Build app again but with Swift Package Manager disabled by pubspec.
|
||||
// App will still use CocoaPods version of integration_test plugin.
|
||||
await _enableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
await _cleanApp(flutterBin, appDirectoryPath);
|
||||
_disableSwiftPackageManagerByPubspec(appDirectoryPath: appDirectoryPath);
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--debug', '-v'],
|
||||
expectedLines: _expectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
unexpectedLines: _unexpectedLines(
|
||||
platform: platformName,
|
||||
appDirectoryPath: appDirectoryPath,
|
||||
cococapodsPlugin: integrationTestPlugin,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await _disableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
ErrorHandlingFileSystem.deleteIfExists(
|
||||
workingDirectory,
|
||||
recursive: true,
|
||||
);
|
||||
}
|
||||
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
|
||||
}
|
||||
|
||||
test('Build $platformName-framework with non-module app uses CocoaPods', () async {
|
||||
final Directory workingDirectory = fileSystem.systemTempDirectory
|
||||
.createTempSync('swift_package_manager_build_framework.');
|
||||
final String workingDirectoryPath = workingDirectory.path;
|
||||
try {
|
||||
// Create and build an app using the Swift Package Manager version of
|
||||
// integration_test.
|
||||
await _enableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
|
||||
final String appDirectoryPath = await _createApp(
|
||||
flutterBin,
|
||||
workingDirectoryPath,
|
||||
iosLanguage: 'swift',
|
||||
platform: platformName,
|
||||
usesSwiftPackageManager: true,
|
||||
options: <String>['--platforms=$platformName'],
|
||||
);
|
||||
_addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin);
|
||||
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[platformName, '--config-only', '-v'],
|
||||
expectedLines: <String>[
|
||||
'Adding Swift Package Manager integration...'
|
||||
]
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childFile('Podfile')
|
||||
.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory(platformName)
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
// Create and build framework using the CocoaPods version of
|
||||
// integration_test even though Swift Package Manager is enabled.
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[
|
||||
'$platformName-framework',
|
||||
'--no-debug',
|
||||
'--no-profile',
|
||||
'-v',
|
||||
],
|
||||
expectedLines: <String>[
|
||||
'Swift Package Manager does not yet support this command. CocoaPods will be used instead.'
|
||||
]
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory('build')
|
||||
.childDirectory(platformName)
|
||||
.childDirectory('framework')
|
||||
.childDirectory('Release')
|
||||
.childDirectory('${integrationTestPlugin.pluginName}.xcframework')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
} finally {
|
||||
await _disableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
ErrorHandlingFileSystem.deleteIfExists(
|
||||
workingDirectory,
|
||||
recursive: true,
|
||||
);
|
||||
}
|
||||
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
|
||||
}
|
||||
|
||||
test('Build ios-framework with module app uses CocoaPods', () async {
|
||||
final Directory workingDirectory = fileSystem.systemTempDirectory
|
||||
.createTempSync('swift_package_manager_build_framework_module.');
|
||||
final String workingDirectoryPath = workingDirectory.path;
|
||||
try {
|
||||
// Create and build module and framework using the CocoaPods version of
|
||||
// integration_test even though Swift Package Manager is enabled.
|
||||
await _enableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
|
||||
final String appDirectoryPath = await _createApp(
|
||||
flutterBin,
|
||||
workingDirectoryPath,
|
||||
iosLanguage: 'swift',
|
||||
platform: 'ios',
|
||||
usesSwiftPackageManager: true,
|
||||
options: <String>['--template=module'],
|
||||
);
|
||||
final _Plugin integrationTestPlugin = _integrationTestPlugin('ios');
|
||||
_addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin);
|
||||
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>['ios', '--config-only', '-v'],
|
||||
unexpectedLines: <String>[
|
||||
'Adding Swift Package Manager integration...'
|
||||
]
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory('.ios')
|
||||
.childFile('Podfile')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory('.ios')
|
||||
.childDirectory('Flutter')
|
||||
.childDirectory('ephemeral')
|
||||
.childDirectory('Packages')
|
||||
.childDirectory('FlutterGeneratedPluginSwiftPackage')
|
||||
.existsSync(),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
await _buildApp(
|
||||
flutterBin,
|
||||
appDirectoryPath,
|
||||
options: <String>[
|
||||
'ios-framework',
|
||||
'--no-debug',
|
||||
'--no-profile',
|
||||
'-v',
|
||||
],
|
||||
unexpectedLines: <String>[
|
||||
'Adding Swift Package Manager integration...',
|
||||
'Swift Package Manager does not yet support this command. CocoaPods will be used instead.'
|
||||
]
|
||||
);
|
||||
|
||||
expect(
|
||||
fileSystem
|
||||
.directory(appDirectoryPath)
|
||||
.childDirectory('build')
|
||||
.childDirectory('ios')
|
||||
.childDirectory('framework')
|
||||
.childDirectory('Release')
|
||||
.childDirectory('${integrationTestPlugin.pluginName}.xcframework')
|
||||
.existsSync(),
|
||||
isTrue,
|
||||
);
|
||||
} finally {
|
||||
await _disableSwiftPackageManager(flutterBin, workingDirectoryPath);
|
||||
ErrorHandlingFileSystem.deleteIfExists(
|
||||
workingDirectory,
|
||||
recursive: true,
|
||||
);
|
||||
}
|
||||
}, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos.
|
||||
}
|
||||
|
||||
Future<void> _enableSwiftPackageManager(
|
||||
String flutterBin,
|
||||
String workingDirectory,
|
||||
) async {
|
||||
final ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'config',
|
||||
'--enable-swift-package-manager',
|
||||
'-v',
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to enable Swift Package Manager: \n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
verbose: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _disableSwiftPackageManager(
|
||||
String flutterBin,
|
||||
String workingDirectory,
|
||||
) async {
|
||||
final ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'config',
|
||||
'--no-enable-swift-package-manager',
|
||||
'-v',
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to disable Swift Package Manager: \n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
verbose: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _createApp(
|
||||
String flutterBin,
|
||||
String workingDirectory, {
|
||||
required String platform,
|
||||
required String iosLanguage,
|
||||
required List<String> options,
|
||||
bool usesSwiftPackageManager = false,
|
||||
}) async {
|
||||
final String appTemplateType = usesSwiftPackageManager ? 'spm' : 'default';
|
||||
|
||||
final String appName = '${platform}_${iosLanguage}_${appTemplateType}_app';
|
||||
final ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--org',
|
||||
'io.flutter.devicelab',
|
||||
'-i',
|
||||
iosLanguage,
|
||||
...options,
|
||||
appName,
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to create app: \n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
|
||||
return fileSystem.path.join(
|
||||
workingDirectory,
|
||||
appName,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _buildApp(
|
||||
String flutterBin,
|
||||
String workingDirectory, {
|
||||
required List<String> options,
|
||||
List<Pattern>? expectedLines,
|
||||
List<String>? unexpectedLines,
|
||||
}) async {
|
||||
final List<Pattern> remainingExpectedLines = expectedLines ?? <Pattern>[];
|
||||
final List<String> unexpectedLinesFound = <String>[];
|
||||
final List<String> command = <String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'build',
|
||||
...options,
|
||||
];
|
||||
|
||||
final ProcessResult result = await processManager.run(
|
||||
command,
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
final List<String> stdout = LineSplitter.split(result.stdout.toString()).toList();
|
||||
final List<String> stderr = LineSplitter.split(result.stderr.toString()).toList();
|
||||
final List<String> output = stdout + stderr;
|
||||
for (final String line in output) {
|
||||
// Remove "[ +3 ms] " prefix
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('[')) {
|
||||
final int prefixEndIndex = trimmedLine.indexOf(']');
|
||||
if (prefixEndIndex > 0) {
|
||||
trimmedLine = trimmedLine
|
||||
.substring(prefixEndIndex + 1, trimmedLine.length)
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
remainingExpectedLines.remove(trimmedLine);
|
||||
remainingExpectedLines.removeWhere((Pattern expectedLine) => trimmedLine.contains(expectedLine));
|
||||
if (unexpectedLines != null && unexpectedLines.contains(trimmedLine)) {
|
||||
unexpectedLinesFound.add(trimmedLine);
|
||||
}
|
||||
}
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to build app for "${command.join(' ')}":\n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
expect(
|
||||
remainingExpectedLines,
|
||||
isEmpty,
|
||||
reason: 'Did not find expected lines for "${command.join(' ')}":\n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
expect(
|
||||
unexpectedLinesFound,
|
||||
isEmpty,
|
||||
reason: 'Found unexpected lines for "${command.join(' ')}":\n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _cleanApp(String flutterBin, String workingDirectory) async {
|
||||
final ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'clean',
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to clean app: \n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
}
|
||||
|
||||
Future<_Plugin> _createPlugin(
|
||||
String flutterBin,
|
||||
String workingDirectory, {
|
||||
required String platform,
|
||||
required String iosLanguage,
|
||||
bool usesSwiftPackageManager = false,
|
||||
}) async {
|
||||
final String dependencyManager = usesSwiftPackageManager ? 'spm' : 'cocoapods';
|
||||
|
||||
// Create plugin
|
||||
final String pluginName = '${platform}_${iosLanguage}_${dependencyManager}_plugin';
|
||||
final ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--org',
|
||||
'io.flutter.devicelab',
|
||||
'--template=plugin',
|
||||
'--platforms=$platform',
|
||||
'-i',
|
||||
iosLanguage,
|
||||
pluginName,
|
||||
],
|
||||
workingDirectory: workingDirectory,
|
||||
);
|
||||
|
||||
expect(
|
||||
result.exitCode,
|
||||
0,
|
||||
reason: 'Failed to create plugin: \n'
|
||||
'stdout: \n${result.stdout}\n'
|
||||
'stderr: \n${result.stderr}\n',
|
||||
);
|
||||
|
||||
final Directory pluginDirectory = fileSystem.directory(
|
||||
fileSystem.path.join(workingDirectory, pluginName),
|
||||
);
|
||||
|
||||
return _Plugin(
|
||||
pluginName: pluginName,
|
||||
pluginPath: pluginDirectory.path,
|
||||
platform: platform,
|
||||
);
|
||||
}
|
||||
|
||||
void _addDependency({
|
||||
required _Plugin plugin,
|
||||
required String appDirectoryPath,
|
||||
}) {
|
||||
final File pubspec = fileSystem.file(
|
||||
fileSystem.path.join(appDirectoryPath, 'pubspec.yaml'),
|
||||
);
|
||||
final String pubspecContent = pubspec.readAsStringSync();
|
||||
pubspec.writeAsStringSync(
|
||||
pubspecContent.replaceFirst(
|
||||
'\ndependencies:\n',
|
||||
'\ndependencies:\n ${plugin.pluginName}:\n path: ${plugin.pluginPath}\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _disableSwiftPackageManagerByPubspec({
|
||||
required String appDirectoryPath,
|
||||
}) {
|
||||
final File pubspec = fileSystem.file(
|
||||
fileSystem.path.join(appDirectoryPath, 'pubspec.yaml'),
|
||||
);
|
||||
final String pubspecContent = pubspec.readAsStringSync();
|
||||
pubspec.writeAsStringSync(
|
||||
pubspecContent.replaceFirst(
|
||||
'\n# The following section is specific to Flutter packages.\nflutter:\n',
|
||||
'\n# The following section is specific to Flutter packages.\nflutter:\n disable-swift-package-manager: true',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_Plugin _integrationTestPlugin(String platform) {
|
||||
final String flutterRoot = getFlutterRoot();
|
||||
return _Plugin(
|
||||
platform: platform,
|
||||
pluginName:
|
||||
(platform == 'ios') ? 'integration_test' : 'integration_test_macos',
|
||||
pluginPath: (platform == 'ios')
|
||||
? fileSystem.path.join(flutterRoot, 'packages', 'integration_test')
|
||||
: fileSystem.path.join(flutterRoot, 'packages', 'integration_test', 'integration_test_macos'),
|
||||
);
|
||||
}
|
||||
|
||||
List<Pattern> _expectedLines({
|
||||
required String platform,
|
||||
required String appDirectoryPath,
|
||||
_Plugin? cococapodsPlugin,
|
||||
_Plugin? swiftPackagePlugin,
|
||||
bool swiftPackageMangerEnabled = false,
|
||||
}) {
|
||||
final String frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS';
|
||||
final String appPlatformDirectoryPath = fileSystem.path.join(
|
||||
appDirectoryPath,
|
||||
platform,
|
||||
);
|
||||
|
||||
final List<Pattern> expectedLines = <Pattern>[];
|
||||
if (swiftPackageMangerEnabled) {
|
||||
expectedLines.addAll(<String>[
|
||||
'FlutterGeneratedPluginSwiftPackage: $appPlatformDirectoryPath/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage',
|
||||
"➜ Explicit dependency on target 'FlutterGeneratedPluginSwiftPackage' in project 'FlutterGeneratedPluginSwiftPackage'",
|
||||
]);
|
||||
}
|
||||
if (swiftPackagePlugin != null) {
|
||||
// If using a Swift Package plugin, but Swift Package Manager is not enabled, it falls back to being used as a CocoaPods plugin.
|
||||
if (swiftPackageMangerEnabled) {
|
||||
expectedLines.addAll(<Pattern>[
|
||||
RegExp('${swiftPackagePlugin.pluginName}: [/private]*${swiftPackagePlugin.pluginPath}/$platform/${swiftPackagePlugin.pluginName} @ local'),
|
||||
"➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project '${swiftPackagePlugin.pluginName}'",
|
||||
]);
|
||||
} else {
|
||||
expectedLines.addAll(<String>[
|
||||
'-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)',
|
||||
"➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project 'Pods'",
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (cococapodsPlugin != null) {
|
||||
expectedLines.addAll(<String>[
|
||||
'Running pod install...',
|
||||
'-> Installing $frameworkName (1.0.0)',
|
||||
'-> Installing ${cococapodsPlugin.pluginName} (0.0.1)',
|
||||
"Target 'Pods-Runner' in project 'Pods'",
|
||||
"➜ Explicit dependency on target '$frameworkName' in project 'Pods'",
|
||||
"➜ Explicit dependency on target '${cococapodsPlugin.pluginName}' in project 'Pods'",
|
||||
]);
|
||||
}
|
||||
return expectedLines;
|
||||
}
|
||||
|
||||
List<String> _unexpectedLines({
|
||||
required String platform,
|
||||
required String appDirectoryPath,
|
||||
_Plugin? cococapodsPlugin,
|
||||
_Plugin? swiftPackagePlugin,
|
||||
bool swiftPackageMangerEnabled = false,
|
||||
}) {
|
||||
final String frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS';
|
||||
final List<String> unexpectedLines = <String>[];
|
||||
if (cococapodsPlugin == null) {
|
||||
unexpectedLines.addAll(<String>[
|
||||
'Running pod install...',
|
||||
'-> Installing $frameworkName (1.0.0)',
|
||||
"Target 'Pods-Runner' in project 'Pods'",
|
||||
]);
|
||||
}
|
||||
if (swiftPackagePlugin != null) {
|
||||
if (swiftPackageMangerEnabled) {
|
||||
unexpectedLines.addAll(<String>[
|
||||
'-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)',
|
||||
"➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project 'Pods'",
|
||||
]);
|
||||
} else {
|
||||
unexpectedLines.addAll(<String>[
|
||||
'${swiftPackagePlugin.pluginName}: ${swiftPackagePlugin.pluginPath}/$platform/${swiftPackagePlugin.pluginName} @ local',
|
||||
"➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project '${swiftPackagePlugin.pluginName}'",
|
||||
]);
|
||||
}
|
||||
}
|
||||
return unexpectedLines;
|
||||
}
|
||||
|
||||
class _Plugin {
|
||||
_Plugin({
|
||||
required this.pluginName,
|
||||
required this.pluginPath,
|
||||
required this.platform,
|
||||
});
|
||||
|
||||
final String pluginName;
|
||||
final String pluginPath;
|
||||
final String platform;
|
||||
String get exampleAppPath => fileSystem.path.join(pluginPath, 'example');
|
||||
String get exampleAppPlatformPath => fileSystem.path.join(exampleAppPath, platform);
|
||||
}
|
@ -315,6 +315,11 @@ class FakePlistParser implements PlistParser {
|
||||
@override
|
||||
String? plistXmlContent(String plistFilePath) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
String? plistJsonContent(String filePath, {bool sorted = false}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object> parseFile(String plistFilePath) {
|
||||
return _underlyingValues;
|
||||
@ -472,6 +477,7 @@ class TestFeatureFlags implements FeatureFlags {
|
||||
this.isCliAnimationEnabled = true,
|
||||
this.isNativeAssetsEnabled = false,
|
||||
this.isPreviewDeviceEnabled = false,
|
||||
this.isSwiftPackageManagerEnabled = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -507,6 +513,9 @@ class TestFeatureFlags implements FeatureFlags {
|
||||
@override
|
||||
final bool isPreviewDeviceEnabled;
|
||||
|
||||
@override
|
||||
final bool isSwiftPackageManagerEnabled;
|
||||
|
||||
@override
|
||||
bool isEnabled(Feature feature) {
|
||||
return switch (feature) {
|
||||
|
@ -15,10 +15,10 @@ LICENSE
|
||||
}
|
||||
s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
|
||||
s.source = { :http => 'https://github.com/flutter/flutter/tree/main/packages/integration_test/integration_test_macos' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.source_files = 'integration_test_macos/Sources/integration_test_macos/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.platform = :osx, '10.14'
|
||||
s.swift_version = '5.0'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
||||
end
|
||||
|
@ -0,0 +1,22 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "integration_test_macos",
|
||||
platforms: [
|
||||
.macOS("10.14"),
|
||||
],
|
||||
products: [
|
||||
.library(name: "integration-test-macos", targets: ["integration_test_macos"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "integration_test_macos",
|
||||
resources: [
|
||||
.process("Resources"),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
@ -15,8 +15,8 @@ LICENSE
|
||||
}
|
||||
s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
|
||||
s.source = { :http => 'https://github.com/flutter/flutter/tree/main/packages/integration_test' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.public_header_files = 'Classes/**/*.h'
|
||||
s.source_files = 'integration_test/Sources/integration_test/**/*.{h,m}'
|
||||
s.public_header_files = 'integration_test/Sources/integration_test/**/*.h'
|
||||
s.dependency 'Flutter'
|
||||
s.ios.framework = 'UIKit'
|
||||
|
||||
|
25
packages/integration_test/ios/integration_test/Package.swift
Normal file
25
packages/integration_test/ios/integration_test/Package.swift
Normal file
@ -0,0 +1,25 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "integration_test",
|
||||
platforms: [
|
||||
.iOS("12.0"),
|
||||
],
|
||||
products: [
|
||||
.library(name: "integration-test", targets: ["integration_test"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "integration_test",
|
||||
resources: [
|
||||
.process("Resources"),
|
||||
],
|
||||
cSettings: [
|
||||
.headerSearchPath("include/integration_test"),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
Loading…
Reference in New Issue
Block a user