Add hidden --no-implicit-pubspec-resolution flag for one stable release. (#157635)

Closes https://github.com/flutter/flutter/issues/157532.

Work towards https://github.com/flutter/flutter/issues/157819.

Because this flag is checked in `FlutterCommand.verifyAndRun`, it seemed cleaner to just make it a global flag - particularly because this flag's longevity is unlikely to be longer than a single stable release.
This commit is contained in:
Matan Lurey 2024-10-30 10:00:32 -07:00 committed by GitHub
parent e10d1dee5d
commit e6c9fce313
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 133 additions and 51 deletions

View File

@ -26,6 +26,7 @@ import '../macos/swift_packages.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import 'create_base.dart';
const String kPlatformHelp =
@ -458,6 +459,7 @@ class CreateCommand extends CreateBase {
macOSPlatform: includeMacos,
windowsPlatform: includeWindows,
webPlatform: includeWeb,
writeLegacyPluginsList: boolArg(FlutterGlobalOptions.kImplicitPubspecResolution, global: true),
);
}
}

View File

@ -21,6 +21,7 @@ import '../plugins.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
/// The function signature of the [print] function.
typedef PrintFn = void Function(Object?);
@ -383,10 +384,15 @@ class PackagesGetCommand extends FlutterCommand {
if (rootProject != null) {
// We need to regenerate the platform specific tooling for both the project
// itself and example(if present).
await rootProject.regeneratePlatformSpecificTooling();
final bool writeLegacyPluginsList = boolArg(FlutterGlobalOptions.kImplicitPubspecResolution, global: true);
await rootProject.regeneratePlatformSpecificTooling(
writeLegacyPluginsList: writeLegacyPluginsList,
);
if (example && rootProject.hasExampleApp && rootProject.example.pubspecFile.existsSync()) {
final FlutterProject exampleProject = rootProject.example;
await exampleProject.regeneratePlatformSpecificTooling();
await exampleProject.regeneratePlatformSpecificTooling(
writeLegacyPluginsList: writeLegacyPluginsList,
);
}
}

View File

@ -1010,7 +1010,7 @@ Future<void> refreshPluginsList(
bool iosPlatform = false,
bool macOSPlatform = false,
bool forceCocoaPodsOnly = false,
bool writeLegacyPluginsList = true,
required bool writeLegacyPluginsList,
}) async {
final List<Plugin> plugins = await findPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files.

View File

@ -340,6 +340,7 @@ class FlutterProject {
Future<void> regeneratePlatformSpecificTooling({
DeprecationBehavior deprecationBehavior = DeprecationBehavior.none,
Iterable<String>? allowedPlugins,
required bool writeLegacyPluginsList,
}) async {
return ensureReadyForPlatformSpecificTooling(
androidPlatform: android.existsSync(),
@ -352,6 +353,7 @@ class FlutterProject {
webPlatform: featureFlags.isWebEnabled && web.existsSync(),
deprecationBehavior: deprecationBehavior,
allowedPlugins: allowedPlugins,
writeLegacyPluginsList: writeLegacyPluginsList,
);
}
@ -366,11 +368,17 @@ class FlutterProject {
bool webPlatform = false,
DeprecationBehavior deprecationBehavior = DeprecationBehavior.none,
Iterable<String>? allowedPlugins,
required bool writeLegacyPluginsList,
}) async {
if (!directory.existsSync() || isPlugin) {
return;
}
await refreshPluginsList(this, iosPlatform: iosPlatform, macOSPlatform: macOSPlatform);
await refreshPluginsList(
this,
iosPlatform: iosPlatform,
macOSPlatform: macOSPlatform,
writeLegacyPluginsList: writeLegacyPluginsList,
);
if (androidPlatform) {
await android.ensureReadyForPlatformSpecificTooling(deprecationBehavior: deprecationBehavior);
}

View File

@ -1790,7 +1790,10 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
// The preview device does not currently support any plugins.
allowedPlugins = PreviewDevice.supportedPubPlugins;
}
await project.regeneratePlatformSpecificTooling(allowedPlugins: allowedPlugins);
await project.regeneratePlatformSpecificTooling(
allowedPlugins: allowedPlugins,
writeLegacyPluginsList: boolArg(FlutterGlobalOptions.kImplicitPubspecResolution, global: true),
);
if (reportNullSafety) {
await _sendNullSafetyAnalyticsEvents(project);
}

View File

@ -33,6 +33,8 @@ abstract final class FlutterGlobalOptions {
static const String kLocalEngineSrcPathOption = 'local-engine-src-path';
static const String kLocalEngineHostOption = 'local-engine-host';
static const String kLocalWebSDKOption = 'local-web-sdk';
// TODO(matanlurey): Remove one stable after https://github.com/flutter/flutter/issues/157819.
static const String kImplicitPubspecResolution = 'implicit-pubspec-resolution';
static const String kMachineFlag = 'machine';
static const String kPackagesOption = 'packages';
static const String kPrefixedErrorsFlag = 'prefixed-errors';
@ -124,6 +126,26 @@ class FlutterCommandRunner extends CommandRunner<void> {
help: 'Print the address of the Dart Tooling Daemon, if one is hosted by the Flutter CLI.',
hide: !verboseHelp,
);
// TODO(matanlurey): Remove after the Q2 2025 stable release; this is intended
// to give application developers a single stable release where the .flutter-plugins
// file is still supported, but is deprecated, and let folks ensure (with CI or local
// testing) that their workflows do not depend on the file.
//
// See https://github.com/flutter/flutter/issues/157532.
argParser.addFlag(FlutterGlobalOptions.kImplicitPubspecResolution,
defaultsTo: true,
help: 'Whether to support (deprecated) implicit pubspec resolution '
'features, each of which are slated for removal in a future stable '
'release. By setting to "true", the following occurs:\n'
' 1. The generation of ".flutter-plugins" (https://flutter.dev/to/flutter-plugins-configuration).\n'
' 2. Including plugins registered as "dev_dependencies" in release mode.\n'
' 3. Flutter localizations are by default output to synthetic "flutter_gen" package.\n'
'\n'
'This flag will become "false" by default, and then features removed.',
hide: !verboseHelp,
);
if (verboseHelp) {
argParser.addSeparator('Local build selection options (not normally required):');
}

View File

@ -72,10 +72,11 @@ void main() {
return projectPath;
}
Future<PackagesCommand> runCommandIn(String projectPath, String verb, { List<String>? args }) async {
Future<PackagesCommand> runCommandIn(String projectPath, String verb, { List<String>? args, List<String>? globalArgs }) async {
final PackagesCommand command = PackagesCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
...?globalArgs,
'packages',
verb,
...?args,
@ -136,15 +137,21 @@ void main() {
'.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
];
const List<String> pluginWitnesses = <String>[
'.flutter-plugins',
'ios/Podfile',
];
List<String> pluginWitnesses({required bool includeLegacyPluginsList}) {
return <String>[
if (includeLegacyPluginsList) '.flutter-plugins',
'.flutter-plugins-dependencies',
'ios/Podfile',
];
}
const List<String> modulePluginWitnesses = <String>[
'.flutter-plugins',
'.ios/Podfile',
];
List<String> modulePluginWitnesses({required bool includeLegacyPluginsList}) {
return <String>[
if (includeLegacyPluginsList) '.flutter-plugins',
'.flutter-plugins-dependencies',
'.ios/Podfile',
];
}
const Map<String, String> pluginContentWitnesses = <String, String>{
'ios/Flutter/Debug.xcconfig': '#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"',
@ -166,7 +173,10 @@ void main() {
for (final String registrant in modulePluginRegistrants) {
expectExists(projectPath, registrant);
}
for (final String witness in pluginWitnesses) {
for (final String witness in pluginWitnesses(includeLegacyPluginsList: true)) {
expectNotExists(projectPath, witness);
}
for (final String witness in modulePluginWitnesses(includeLegacyPluginsList: true)) {
expectNotExists(projectPath, witness);
}
modulePluginContentWitnesses.forEach((String witness, String content) {
@ -174,23 +184,26 @@ void main() {
});
}
void expectPluginInjected(String projectPath) {
void expectPluginInjected(String projectPath, {required bool includeLegacyPluginsList}) {
for (final String registrant in pluginRegistrants) {
expectExists(projectPath, registrant);
}
for (final String witness in pluginWitnesses) {
for (final String witness in pluginWitnesses(includeLegacyPluginsList: includeLegacyPluginsList)) {
expectExists(projectPath, witness);
}
if (!includeLegacyPluginsList) {
expectNotExists(projectPath, '.flutter-plugins');
}
pluginContentWitnesses.forEach((String witness, String content) {
expectContains(projectPath, witness, content);
});
}
void expectModulePluginInjected(String projectPath) {
void expectModulePluginInjected(String projectPath, {required bool includeLegacyPluginsList}) {
for (final String registrant in modulePluginRegistrants) {
expectExists(projectPath, registrant);
}
for (final String witness in modulePluginWitnesses) {
for (final String witness in modulePluginWitnesses(includeLegacyPluginsList: includeLegacyPluginsList)) {
expectExists(projectPath, witness);
}
modulePluginContentWitnesses.forEach((String witness, String content) {
@ -202,7 +215,7 @@ void main() {
final Iterable<String> allFiles = <List<String>>[
pubOutput,
modulePluginRegistrants,
pluginWitnesses,
pluginWitnesses(includeLegacyPluginsList: true),
].expand<String>((List<String> list) => list);
for (final String path in allFiles) {
final File file = globals.fs.file(globals.fs.path.join(projectPath, path));
@ -579,7 +592,7 @@ workspace:
await runCommandIn(projectPath, 'get');
expectDependenciesResolved(projectPath);
expectModulePluginInjected(projectPath);
expectModulePluginInjected(projectPath, includeLegacyPluginsList: true);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
@ -609,7 +622,35 @@ workspace:
await runCommandIn(exampleProjectPath, 'get');
expectDependenciesResolved(exampleProjectPath);
expectPluginInjected(exampleProjectPath);
expectPluginInjected(exampleProjectPath, includeLegacyPluginsList: true);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(
fileSystem: globals.fs,
logger: globals.logger,
processManager: globals.processManager,
usage: globals.flutterUsage,
botDetector: globals.botDetector,
platform: globals.platform,
stdio: mockStdio,
),
});
testUsingContext('get --no-implicit-pubspec-resolution omits ".flutter-plugins"', () async {
final String projectPath = await createProject(
tempDir,
arguments: <String>['--template=plugin', '--no-pub', '--platforms=ios,android'],
);
final String exampleProjectPath = globals.fs.path.join(projectPath, 'example');
removeGeneratedFiles(projectPath);
removeGeneratedFiles(exampleProjectPath);
// Running flutter packages get also resolves the dependencies in the example/ project.
await runCommandIn(projectPath, 'get', globalArgs: <String>['--no-implicit-pubspec-resolution']);
expectDependenciesResolved(projectPath);
expectDependenciesResolved(exampleProjectPath);
expectPluginInjected(exampleProjectPath, includeLegacyPluginsList: false);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Pub: () => Pub.test(

View File

@ -402,7 +402,7 @@ dependencies:
group('refreshPlugins', () {
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async {
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
@ -415,7 +415,7 @@ dependencies:
flutterProject.flutterPluginsFile.createSync();
flutterProject.flutterPluginsDependenciesFile.createSync();
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
@ -434,7 +434,7 @@ dependencies:
iosProject.testExists = true;
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsFile.existsSync(), true);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
@ -473,7 +473,7 @@ dependencies:
final DateTime dateCreated = DateTime(1970);
systemClock.currentTime = dateCreated;
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
// Verify .flutter-plugins-dependencies is configured correctly.
expect(flutterProject.flutterPluginsFile.existsSync(), true);
@ -583,7 +583,7 @@ dependencies:
final DateTime dateCreated = DateTime(1970);
systemClock.currentTime = dateCreated;
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
final String pluginsString = flutterProject.flutterPluginsDependenciesFile.readAsStringSync();
@ -655,7 +655,7 @@ dependencies:
flutterProject.usesSwiftPackageManager = true;
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
@ -692,7 +692,7 @@ dependencies:
flutterProject.usesSwiftPackageManager = true;
await refreshPluginsList(flutterProject, forceCocoaPodsOnly: true);
await refreshPluginsList(flutterProject, forceCocoaPodsOnly: true, writeLegacyPluginsList: true);
expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
@ -714,7 +714,7 @@ dependencies:
iosProject.testExists = true;
macosProject.exists = true;
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true, writeLegacyPluginsList: true);
expect(iosProject.podManifestLock.existsSync(), false);
expect(macosProject.podManifestLock.existsSync(), false);
}, overrides: <Type, Generator>{
@ -733,11 +733,11 @@ dependencies:
// Since there was no plugins list, the lock files will be invalidated.
// The second call is where the plugins list is compared to the existing one, and if there is no change,
// the podfiles shouldn't be invalidated.
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true, writeLegacyPluginsList: true);
simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject);
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(iosProject.podManifestLock.existsSync(), true);
expect(macosProject.podManifestLock.existsSync(), true);
}, overrides: <Type, Generator>{
@ -1508,7 +1508,7 @@ The Flutter Preview device does not support the following plugins from your pubs
linuxProject.exists = true;
createFakePlugin(fs);
// refreshPluginsList should call createPluginSymlinks.
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(linuxProject.pluginSymlinkDirectory.childLink('some_plugin').existsSync(), true);
}, overrides: <Type, Generator>{
@ -1521,7 +1521,7 @@ The Flutter Preview device does not support the following plugins from your pubs
windowsProject.exists = true;
createFakePlugin(fs);
// refreshPluginsList should call createPluginSymlinks.
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
expect(windowsProject.pluginSymlinkDirectory.childLink('some_plugin').existsSync(), true);
}, overrides: <Type, Generator>{
@ -1567,7 +1567,7 @@ The Flutter Preview device does not support the following plugins from your pubs
// refreshPluginsList should remove existing links and recreate on changes.
createFakePlugin(fs);
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
for (final File file in dummyFiles) {
expect(file.existsSync(), false);
@ -1606,7 +1606,7 @@ The Flutter Preview device does not support the following plugins from your pubs
linuxProject.exists = true;
windowsProject.exists = true;
createFakePlugin(fs);
await refreshPluginsList(flutterProject);
await refreshPluginsList(flutterProject, writeLegacyPluginsList: true);
final List<Link> links = <Link>[
linuxProject.pluginSymlinkDirectory.childLink('some_plugin'),

View File

@ -141,12 +141,12 @@ void main() {
FlutterManifest.empty(logger: logger),
FlutterManifest.empty(logger: logger),
);
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectNotExists(project.directory);
});
_testInMemory('does nothing in plugin or package root project', () async {
final FlutterProject project = await aPluginProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
@ -158,7 +158,7 @@ void main() {
// that a project was a plugin, but shouldn't be as this creates false
// positives.
project.directory.childDirectory('example').createSync();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
@ -166,22 +166,22 @@ void main() {
});
_testInMemory('injects plugins for iOS', () async {
final FlutterProject project = await someProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
});
_testInMemory('generates Xcode configuration for iOS', () async {
final FlutterProject project = await someProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
});
_testInMemory('injects plugins for Android', () async {
final FlutterProject project = await someProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
});
_testInMemory('updates local properties for Android', () async {
final FlutterProject project = await someProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
_testInMemory('checkForDeprecation fails on invalid android app manifest file', () async {
@ -224,18 +224,18 @@ void main() {
final FlutterProject project = await aPluginProject();
project.example.directory.deleteSync();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expect(testLogger.statusText, isNot(contains('https://github.com/flutter/flutter/blob/main/docs/platforms/android/Upgrading-pre-1.12-Android-projects.md')));
});
_testInMemory('updates local properties for Android', () async {
final FlutterProject project = await someProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
testUsingContext('injects plugins for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.macos.pluginRegistrantImplementation);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
@ -249,7 +249,7 @@ void main() {
testUsingContext('generates Xcode configuration for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.macos.generatedXcodePropertiesFile);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
@ -263,7 +263,7 @@ void main() {
testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject();
project.linux.cmakeFile.createSync(recursive: true);
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
@ -278,7 +278,7 @@ void main() {
testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject();
project.windows.cmakeFile.createSync(recursive: true);
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
@ -292,14 +292,14 @@ void main() {
});
_testInMemory('creates Android library in module', () async {
final FlutterProject project = await aModuleProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
});
_testInMemory('creates iOS pod in module', () async {
final FlutterProject project = await aModuleProject();
await project.regeneratePlatformSpecificTooling();
await project.regeneratePlatformSpecificTooling(writeLegacyPluginsList: true);
final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter');
expectExists(flutter.childFile('podhelper.rb'));
expectExists(flutter.childFile('flutter_export_environment.sh'));