Skip to content

Commit 08d8a7f

Browse files
authored
Enable Swift Package Manager by default on master channel (flutter#152049)
Changes: 1. Enables Swift Package Manager by default on the main/master channel 2. Fixes tests that fail if Swift Package Manager is enabled Corresponding docs change: flutter/website#10938 Addresses flutter#151567
1 parent 0d50491 commit 08d8a7f

File tree

9 files changed

+216
-86
lines changed

9 files changed

+216
-86
lines changed

dev/devicelab/bin/tasks/build_ios_framework_module_test.dart

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
151151

152152
section('Check debug build has no Dart AOT');
153153

154-
final String aotSymbols = await _dylibSymbols(debugAppFrameworkPath);
154+
final String aotSymbols = await dumpSymbolTable(debugAppFrameworkPath);
155155

156156
if (aotSymbols.contains('architecture') ||
157157
aotSymbols.contains('_kDartVmSnapshot')) {
@@ -172,7 +172,7 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
172172

173173
await _checkDylib(appFrameworkPath);
174174

175-
final String aotSymbols = await _dylibSymbols(appFrameworkPath);
175+
final String aotSymbols = await dumpSymbolTable(appFrameworkPath);
176176

177177
if (!aotSymbols.contains('_kDartVmSnapshot')) {
178178
throw TaskResult.failure('$mode App.framework missing Dart AOT');
@@ -562,7 +562,7 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
562562

563563
section('Check debug build has no Dart AOT');
564564

565-
final String aotSymbols = await _dylibSymbols(debugAppFrameworkPath);
565+
final String aotSymbols = await dumpSymbolTable(debugAppFrameworkPath);
566566

567567
if (aotSymbols.contains('architecture') ||
568568
aotSymbols.contains('_kDartVmSnapshot')) {
@@ -583,7 +583,7 @@ Future<void> _testBuildMacOSFramework(Directory projectDir) async {
583583

584584
await _checkDylib(appFrameworkPath);
585585

586-
final String aotSymbols = await _dylibSymbols(appFrameworkPath);
586+
final String aotSymbols = await dumpSymbolTable(appFrameworkPath);
587587

588588
if (!aotSymbols.contains('_kDartVmSnapshot')) {
589589
throw TaskResult.failure('$mode App.framework missing Dart AOT');
@@ -939,15 +939,6 @@ Future<void> _checkStatic(String pathToLibrary) async {
939939
}
940940
}
941941

942-
Future<String> _dylibSymbols(String pathToDylib) {
943-
return eval('nm', <String>[
944-
'-g',
945-
pathToDylib,
946-
'-arch',
947-
'arm64',
948-
]);
949-
}
950-
951942
Future<bool> _linksOnFlutter(String pathToBinary) async {
952943
final String loadCommands = await eval('otool', <String>[
953944
'-l',

dev/devicelab/bin/tasks/module_test_ios.dart

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -703,13 +703,7 @@ Future<bool> _isAppAotBuild(Directory app) async {
703703
'App',
704704
);
705705

706-
final String symbolTable = await eval(
707-
'nm',
708-
<String> [
709-
'-gU',
710-
binary,
711-
],
712-
);
706+
final String symbolTable = await dumpSymbolTable(binary);
713707

714708
return symbolTable.contains('kDartIsolateSnapshotInstructions');
715709
}

dev/devicelab/bin/tasks/plugin_dependencies_test.dart

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66
import 'dart:io';
77

88
import 'package:flutter_devicelab/framework/framework.dart';
9+
import 'package:flutter_devicelab/framework/ios.dart';
910
import 'package:flutter_devicelab/framework/task_result.dart';
1011
import 'package:flutter_devicelab/framework/utils.dart';
1112
import 'package:path/path.dart' as path;
@@ -209,6 +210,7 @@ public class DummyPluginAClass {
209210
final String flutterPluginsDependenciesFileContent = flutterPluginsDependenciesFile.readAsStringSync();
210211

211212
final Map<String, dynamic> jsonContent = json.decode(flutterPluginsDependenciesFileContent) as Map<String, dynamic>;
213+
final bool swiftPackageManagerEnabled = jsonContent['swift_package_manager_enabled'] as bool? ?? false;
212214

213215
// Verify the dependencyGraph object is valid. The rest of the contents of this file are not relevant to the
214216
// dependency graph and are tested by unit tests.
@@ -302,28 +304,35 @@ public class DummyPluginAClass {
302304
return TaskResult.failure('Failed to build plugin A example iOS app');
303305
}
304306

305-
checkDirectoryExists(path.join(
306-
appBundle.path,
307-
'Frameworks',
308-
'plugin_a.framework',
309-
));
310-
checkDirectoryExists(path.join(
311-
appBundle.path,
312-
'Frameworks',
313-
'plugin_b.framework',
314-
));
315-
checkDirectoryExists(path.join(
316-
appBundle.path,
317-
'Frameworks',
318-
'plugin_c.framework',
319-
));
320-
321-
// Plugin D is Android only and should not be embedded.
322-
checkDirectoryNotExists(path.join(
323-
appBundle.path,
324-
'Frameworks',
325-
'plugin_d.framework',
326-
));
307+
if (swiftPackageManagerEnabled) {
308+
// Check plugins are built statically if using SwiftPM.
309+
final String executable = path.join(appBundle.path, 'Runner');
310+
final String symbols = await dumpSymbolTable(executable);
311+
312+
final bool foundA = symbols.contains('plugin_a');
313+
final bool foundB = symbols.contains('plugin_b');
314+
final bool foundC = symbols.contains('plugin_c');
315+
final bool foundD = symbols.contains('plugin_d');
316+
317+
if (!foundA || !foundB || !foundC) {
318+
return TaskResult.failure(
319+
'Failed to find plugins_a, plugin_b, or plugin_c symbols in the app'
320+
);
321+
}
322+
323+
if (foundD) {
324+
return TaskResult.failure(
325+
'Found Android plugin_d symbols in iOS app'
326+
);
327+
}
328+
} else {
329+
// Check plugins are built dynamically if using CocoaPods.
330+
checkDirectoryExists(path.join(appBundle.path, 'Frameworks', 'plugin_a.framework'));
331+
checkDirectoryExists(path.join(appBundle.path, 'Frameworks', 'plugin_b.framework'));
332+
checkDirectoryExists(path.join(appBundle.path, 'Frameworks', 'plugin_c.framework'));
333+
334+
checkDirectoryNotExists(path.join(appBundle.path, 'Frameworks', 'plugin_d.framework'));
335+
}
327336
}
328337

329338
return TaskResult.success(null);

dev/devicelab/lib/framework/ios.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,17 @@ File? _createDisabledSandboxEntitlementFile(
308308

309309
return disabledSandboxEntitlementFile;
310310
}
311+
312+
/// Returns global (external) symbol table entries, delimited by new lines.
313+
Future<String> dumpSymbolTable(String filePath) {
314+
return eval(
315+
'nm',
316+
<String>[
317+
'--extern-only',
318+
'--just-symbol-name',
319+
filePath,
320+
'-arch',
321+
'arm64',
322+
],
323+
);
324+
}

dev/devicelab/lib/tasks/plugin_tests.dart

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class PluginTest {
7777
final _FlutterProject app = await _FlutterProject.create(tempDir, options, buildTarget,
7878
name: 'plugintestapp', template: 'app', environment: appCreateEnvironment);
7979
try {
80+
if (cocoapodsTransitiveFlutterDependency) {
81+
section('Disable Swift Package Manager');
82+
await app.disableSwiftPackageManager();
83+
}
84+
8085
section('Add plugins');
8186
await app.addPlugin('plugintest',
8287
pluginPath: path.join('..', 'plugintest'));
@@ -147,6 +152,20 @@ class _FlutterProject {
147152
return _FlutterProject(Directory(path.join(rootPath)), 'example');
148153
}
149154

155+
Future<void> disableSwiftPackageManager() async {
156+
final File pubspec = pubspecFile;
157+
String content = await pubspec.readAsString();
158+
content = content.replaceFirst(
159+
'# The following section is specific to Flutter packages.\n'
160+
'flutter:\n',
161+
'# The following section is specific to Flutter packages.\n'
162+
'flutter:\n'
163+
'\n'
164+
' disable-swift-package-manager: true\n'
165+
);
166+
await pubspec.writeAsString(content, flush: true);
167+
}
168+
150169
Future<void> addPlugin(String plugin, {String? pluginPath}) async {
151170
final File pubspec = pubspecFile;
152171
String content = await pubspec.readAsString();
@@ -244,9 +263,14 @@ class $dartPluginClass {
244263
await podspec.writeAsString(podspecContent, flush: true);
245264

246265
// Make PlugintestPlugin.swift compile on iOS and macOS with target conditionals.
266+
// If SwiftPM is disabled, the file will be in `darwin/Classes/`.
267+
// Otherwise, the file will be in `darwin/<plugin>/Sources/<plugin>/`.
247268
final String pluginClass = '${name[0].toUpperCase()}${name.substring(1)}Plugin';
248269
print('pluginClass: $pluginClass');
249-
final File pluginRegister = File(path.join(darwinDir.path, 'Classes', '$pluginClass.swift'));
270+
File pluginRegister = File(path.join(darwinDir.path, 'Classes', '$pluginClass.swift'));
271+
if (!pluginRegister.existsSync()) {
272+
pluginRegister = File(path.join(darwinDir.path, name, 'Sources', name, '$pluginClass.swift'));
273+
}
250274
final String pluginRegisterContent = '''
251275
#if os(macOS)
252276
import FlutterMacOS
@@ -494,42 +518,55 @@ s.dependency 'AppAuth', '1.6.0'
494518
}
495519

496520
if (validateNativeBuildProject) {
497-
final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'));
498-
if (!podsProject.existsSync()) {
499-
throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}');
500-
}
501-
502-
final String podsProjectContent = podsProject.readAsStringSync();
503-
if (target == 'ios') {
504-
// Plugins with versions lower than the app version should not have IPHONEOS_DEPLOYMENT_TARGET set.
505-
// The plugintest plugin target should not have IPHONEOS_DEPLOYMENT_TARGET set since it has been lowered
506-
// in _reduceDarwinPluginMinimumVersion to 10, which is below the target version of 11.
507-
if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 10')) {
508-
throw TaskResult.failure('Plugin build setting IPHONEOS_DEPLOYMENT_TARGET not removed');
509-
}
510-
// Transitive dependency AppAuth targeting too-low 8.0 was not fixed.
511-
if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 8')) {
512-
throw TaskResult.failure('Transitive dependency build setting IPHONEOS_DEPLOYMENT_TARGET=8 not removed');
521+
final File generatedSwiftManifest = File(path.join(
522+
rootPath,
523+
target,
524+
'Flutter',
525+
'ephemeral',
526+
'Packages',
527+
'FlutterGeneratedPluginSwiftPackage',
528+
'Package.swift'
529+
));
530+
final bool swiftPackageManagerEnabled = generatedSwiftManifest.existsSync();
531+
532+
if (!swiftPackageManagerEnabled) {
533+
final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'));
534+
if (!podsProject.existsSync()) {
535+
throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}');
513536
}
514-
if (!podsProjectContent.contains(r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";')) {
515-
throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"');
516-
}
517-
} else if (target == 'macos') {
518-
// Same for macOS deployment target, but 10.8.
519-
// The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set.
520-
if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) {
521-
throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed');
522-
}
523-
// Transitive dependency AppAuth targeting too-low 10.9 was not fixed.
524-
if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.9')) {
525-
throw TaskResult.failure('Transitive dependency build setting MACOSX_DEPLOYMENT_TARGET=10.9 not removed');
537+
538+
final String podsProjectContent = podsProject.readAsStringSync();
539+
if (target == 'ios') {
540+
// Plugins with versions lower than the app version should not have IPHONEOS_DEPLOYMENT_TARGET set.
541+
// The plugintest plugin target should not have IPHONEOS_DEPLOYMENT_TARGET set since it has been lowered
542+
// in _reduceDarwinPluginMinimumVersion to 10, which is below the target version of 11.
543+
if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 10')) {
544+
throw TaskResult.failure('Plugin build setting IPHONEOS_DEPLOYMENT_TARGET not removed');
545+
}
546+
// Transitive dependency AppAuth targeting too-low 8.0 was not fixed.
547+
if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 8')) {
548+
throw TaskResult.failure('Transitive dependency build setting IPHONEOS_DEPLOYMENT_TARGET=8 not removed');
549+
}
550+
if (!podsProjectContent.contains(r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";')) {
551+
throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"');
552+
}
553+
} else if (target == 'macos') {
554+
// Same for macOS deployment target, but 10.8.
555+
// The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set.
556+
if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) {
557+
throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed');
558+
}
559+
// Transitive dependency AppAuth targeting too-low 10.9 was not fixed.
560+
if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.9')) {
561+
throw TaskResult.failure('Transitive dependency build setting MACOSX_DEPLOYMENT_TARGET=10.9 not removed');
562+
}
526563
}
527-
}
528564

529-
if (localEngine != null) {
530-
final RegExp localEngineSearchPath = RegExp('FRAMEWORK_SEARCH_PATHS\\s*=[^;]*${localEngine.path}');
531-
if (!localEngineSearchPath.hasMatch(podsProjectContent)) {
532-
throw TaskResult.failure('FRAMEWORK_SEARCH_PATHS does not contain the --local-engine path');
565+
if (localEngine != null) {
566+
final RegExp localEngineSearchPath = RegExp('FRAMEWORK_SEARCH_PATHS\\s*=[^;]*${localEngine.path}');
567+
if (!localEngineSearchPath.hasMatch(podsProjectContent)) {
568+
throw TaskResult.failure('FRAMEWORK_SEARCH_PATHS does not contain the --local-engine path');
569+
}
533570
}
534571
}
535572
}

packages/flutter_tools/lib/src/features.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ const Feature swiftPackageManager = Feature(
186186
environmentOverride: 'SWIFT_PACKAGE_MANAGER',
187187
master: FeatureChannelSetting(
188188
available: true,
189+
enabledByDefault: true,
189190
),
190191
);
191192

packages/flutter_tools/test/commands.shard/permeable/create_test.dart

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ void main() {
693693
),
694694
});
695695

696-
testUsingContext('kotlin/swift plugin project', () async {
696+
testUsingContext('kotlin/swift plugin project without Swift Package Manager', () async {
697697
return _createProject(
698698
projectDir,
699699
<String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift', '--platforms', 'ios,android'],
@@ -718,6 +718,9 @@ void main() {
718718
'ios/Classes/FlutterProjectPlugin.m',
719719
],
720720
);
721+
}, overrides: <Type, Generator>{
722+
// Test flags disable Swift Package Manager.
723+
FeatureFlags: () => TestFeatureFlags(),
721724
});
722725

723726
testUsingContext('swift plugin project with Swift Package Manager', () async {
@@ -1944,7 +1947,7 @@ void main() {
19441947
);
19451948
});
19461949

1947-
testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async {
1950+
testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org, without Swift Package Manager', () async {
19481951
await _createProject(
19491952
projectDir,
19501953
<String>[
@@ -1969,13 +1972,56 @@ void main() {
19691972
unexpectedPaths: <String>[
19701973
'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
19711974
'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
1975+
'ios/flutter_project/Sources/flutter_project/include/flutter_project/FlutterProjectPlugin.h',
19721976
],
19731977
);
19741978
final FlutterProject project = FlutterProject.fromDirectory(projectDir);
19751979
expect(
19761980
await project.example.ios.productBundleIdentifier(BuildInfo.debug),
19771981
'com.bar.foo.flutterProjectExample',
19781982
);
1983+
}, overrides: <Type, Generator>{
1984+
// Test flags disable Swift Package Manager.
1985+
FeatureFlags: () => TestFeatureFlags(),
1986+
});
1987+
1988+
testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org, with Swift Package Manager', () async {
1989+
await _createProject(
1990+
projectDir,
1991+
<String>[
1992+
'--no-pub',
1993+
'--template=plugin',
1994+
'--org', 'com.bar.foo',
1995+
'-i', 'objc',
1996+
'-a', 'java',
1997+
'--platforms', 'ios,android',
1998+
],
1999+
<String>[],
2000+
);
2001+
projectDir.childDirectory('example').deleteSync(recursive: true);
2002+
projectDir.childDirectory('ios').deleteSync(recursive: true);
2003+
await _createProject(
2004+
projectDir,
2005+
<String>['--no-pub', '--template=plugin', '-i', 'objc', '-a', 'java', '--platforms', 'ios,android'],
2006+
<String>[
2007+
'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
2008+
'ios/flutter_project/Sources/flutter_project/include/flutter_project/FlutterProjectPlugin.h',
2009+
],
2010+
unexpectedPaths: <String>[
2011+
'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
2012+
'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
2013+
'ios/Classes/FlutterProjectPlugin.h',
2014+
],
2015+
);
2016+
final FlutterProject project = FlutterProject.fromDirectory(projectDir);
2017+
expect(
2018+
await project.example.ios.productBundleIdentifier(BuildInfo.debug),
2019+
'com.bar.foo.flutterProjectExample',
2020+
);
2021+
}, overrides: <Type, Generator>{
2022+
FeatureFlags: () => TestFeatureFlags(
2023+
isSwiftPackageManagerEnabled: true,
2024+
),
19792025
});
19802026

19812027
testUsingContext('fails to re-gen without specified org when org is ambiguous', () async {

packages/flutter_tools/test/general.shard/features_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ void main() {
404404
});
405405

406406
test('${swiftPackageManager.name} availability and default enabled', () {
407-
expect(swiftPackageManager.master.enabledByDefault, false);
407+
expect(swiftPackageManager.master.enabledByDefault, true);
408408
expect(swiftPackageManager.master.available, true);
409409
expect(swiftPackageManager.beta.enabledByDefault, false);
410410
expect(swiftPackageManager.beta.available, false);

0 commit comments

Comments
 (0)