flutter/packages/flutter_tools/test/general.shard/xcode_project_test.dart
Victoria Ashworth 6d19fa3bfa
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
2024-04-18 21:12:36 +00:00

214 lines
7.3 KiB
Dart

// 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(),
);
}
}