// 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:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/update_packages.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:test/fake.dart'; import '../../src/context.dart'; import '../../src/test_flutter_command_runner.dart'; // An example pubspec.yaml from flutter, not necessary for it to be up to date. const kFlutterWorkspacePubspecYaml = r''' name: flutter description: A framework for writing Flutter applications homepage: http://flutter.dev environment: sdk: ^3.7.0-0 workspace: - packages/flutter - examples dependencies: # To update these, use "flutter update-packages --force-upgrade". collection: 1.14.11 meta: 1.1.8 typed_data: ^1.1.6 vector_math: 2.0.8 test_api: 0.7.4 sky_engine: sdk: flutter gallery: git: url: https://github.com/flutter/gallery.git ref: d00362e6bdd0f9b30bba337c358b9e4a6e4ca950 dev_dependencies: flutter_test: sdk: flutter flutter_goldens: sdk: flutter archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" # PUBSPEC CHECKSUM: u6pfsu '''; const kWidgetTestPubspecYaml = r''' name: widget_preview_scaffold description: Scaffolding for Flutter Widget Previews publish_to: "none" version: 0.0.1 environment: sdk: ^3.8.0-265.0.dev dependencies: flutter: sdk: flutter flutter_test: sdk: flutter # These will be replaced with proper constraints after the template is hydrated. dtd: 4.0.0 flutter_lints: 6.0.0 google_fonts: 6.3.2 json_rpc_2: 3.0.3 path: 1.9.1 stack_trace: 1.12.1 url_launcher: 6.3.2 unified_analytics: 8.0.5 dev_dependencies: flutter_tools: path: ../../../packages/flutter_tools/ # PUBSPEC CHECKSUM: 55t4hi '''; // An example pubspec.yaml from flutter, not necessary for it to be up to date. const kNonWorkspacePubspecYaml = r''' name: hook_user_defines description: 'Project for testing user-defines.' version: 0.0.1 environment: sdk: ^3.9.0-0 hooks: user_defines: hook_user_defines: # package name magic_value: 1000 dependencies: hooks: 1.0.0 logging: 1.3.0 native_toolchain_c: 0.17.4 dev_dependencies: test: 1.28.0 # PUBSPEC CHECKSUM: qlfuuh '''; // An example pubspec.yaml from flutter, not necessary for it to be up to date. const kFlutterPubspecYaml = r''' name: flutter description: A framework for writing Flutter applications homepage: http://flutter.dev environment: sdk: ^3.7.0-0 resolution: workspace dependencies: # To update these, use "flutter update-packages --force-upgrade". collection: 1.14.11 meta: 1.1.8 typed_data: ^1.1.6 vector_math: 2.0.8 sky_engine: sdk: flutter gallery: git: url: https://github.com/flutter/gallery.git ref: d00362e6bdd0f9b30bba337c358b9e4a6e4ca950 dev_dependencies: flutter_test: sdk: flutter flutter_goldens: sdk: flutter archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" # PUBSPEC CHECKSUM: dn2ahm '''; // An example pubspec.yaml, not necessary for it to be up to date. const kFlutterToolsPubspecYaml = r''' name: flutter_tools description: Examples for flutter homepage: http://flutter.dev version: 1.0.0 resolution: workspace environment: sdk: '>=3.2.0-0 <4.0.0' flutter: ">=2.5.0-6.0.pre.30 <3.0.0" dependencies: test_api: 0.7.4 flutter: sdk: flutter archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" unified_analytics: 8.0.10 # PUBSPEC CHECKSUM: 6hijp0 '''; // An example pubspec.yaml, not necessary for it to be up to date. const kExamplesPubspecYaml = r''' name: examples description: Examples for flutter homepage: http://flutter.dev version: 1.0.0 resolution: workspace environment: sdk: '>=3.2.0-0 <4.0.0' flutter: ">=2.5.0-6.0.pre.30 <3.0.0" dependencies: cupertino_icons: 1.0.4 flutter: sdk: flutter archive: 3.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" # PUBSPEC CHECKSUM: ivm9uf '''; const kVersionJson = ''' { "frameworkVersion": "1.2.3", "channel": "[user-branch]", "repositoryUrl": "git@github.com:flutter/flutter.git", "frameworkRevision": "1234567812345678123456781234567812345678", "frameworkCommitDate": "2024-02-06 22:26:52 +0100", "engineRevision": "abcdef01abcdef01abcdef01abcdef01abcdef01", "dartSdkVersion": "1.2.3", "devToolsVersion": "1.2.3", "flutterVersion": "1.2.3" } '''; void main() { group('update-packages', () { late FileSystem fileSystem; late Directory flutterSdk; late Directory flutterTools; late Directory widgetPreviewScaffold; late Directory hookUserDefinesIntegrationTest; late _FakePub pub; late FakeProcessManager processManager; late BufferLogger logger; setUpAll(() { Cache.disableLocking(); logger = BufferLogger.test(); }); setUp(() { fileSystem = MemoryFileSystem.test(); flutterSdk = fileSystem.directory('flutter')..createSync(); flutterSdk.childFile('version').writeAsStringSync('1.2.3'); flutterSdk.childDirectory('bin').childDirectory('cache').childFile('flutter.version.json') ..createSync(recursive: true) ..writeAsStringSync(kVersionJson); flutterSdk.childDirectory('dev').createSync(recursive: true); flutterSdk.childDirectory('examples').childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kExamplesPubspecYaml); flutterSdk.childDirectory('packages').childDirectory('flutter_test').childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kFlutterToolsPubspecYaml); flutterSdk .childDirectory('packages') .childDirectory('flutter_localizations') .childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kFlutterToolsPubspecYaml); flutterTools = flutterSdk.childDirectory('packages').childDirectory('flutter_tools'); flutterTools.childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kFlutterToolsPubspecYaml); flutterSdk.childDirectory('packages').childDirectory('flutter').childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kFlutterPubspecYaml); flutterSdk.childFile('pubspec.yaml') ..createSync() ..writeAsStringSync(kFlutterWorkspacePubspecYaml); widgetPreviewScaffold = flutterSdk .childDirectory('dev') .childDirectory('integration_tests') .childDirectory('widget_preview_scaffold'); widgetPreviewScaffold.childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kWidgetTestPubspecYaml); hookUserDefinesIntegrationTest = flutterSdk .childDirectory('dev') .childDirectory('integration_tests') .childDirectory('hook_user_defines'); hookUserDefinesIntegrationTest.childFile('pubspec.yaml') ..createSync(recursive: true) ..writeAsStringSync(kNonWorkspacePubspecYaml); Cache.flutterRoot = flutterSdk.absolute.path; pub = _FakePub(); processManager = FakeProcessManager.empty(); }); testUsingContext( 'updates packages - only runs pub get', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner(command).run(['update-packages']); expect( pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies, Pubspec.parse(kFlutterWorkspacePubspecYaml).dependencies, ); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), }, ); testUsingContext( '--force-upgrade updates packages', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner(command).run(['update-packages', '--force-upgrade']); expect( pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies, (Pubspec.parse(kFlutterWorkspacePubspecYaml) ..dependencies['typed_data'] = HostedDependency( version: VersionConstraint.parse('^1.1.1'), )) .dependencies, ); expect( pub.pubspecs[flutterTools.absolute.path]!.first.dependencies, (Pubspec.parse(kFlutterToolsPubspecYaml) ..dependencies['unified_analytics'] = HostedDependency( version: VersionConstraint.parse('8.0.10'), )) .dependencies, ); expect( pub.pubspecs[widgetPreviewScaffold.absolute.path]!.first.dependencies, (Pubspec.parse(kWidgetTestPubspecYaml) ..dependencies['unified_analytics'] = HostedDependency( version: VersionConstraint.parse('8.0.10'), )) .dependencies, ); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), }, ); testUsingContext( '--cherry-pick-package', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner( command, ).run(['update-packages', '--cherry-pick=vector_math:2.0.9']); expect( pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies, (Pubspec.parse(kFlutterWorkspacePubspecYaml) ..dependencies['vector_math'] = HostedDependency( version: VersionConstraint.parse('2.0.9'), )) .dependencies, ); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), Logger: () => logger, }, ); testUsingContext( '--cherry-pick-package with caret', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner( command, ).run(['update-packages', '--cherry-pick=vector_math:^2.0.9']); expect( pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies, (Pubspec.parse(kFlutterWorkspacePubspecYaml) ..dependencies['vector_math'] = HostedDependency( version: VersionConstraint.parse('^2.0.9'), )) .dependencies, ); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), Logger: () => logger, }, ); testUsingContext( '--cherry-pick-package muliple', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner( command, ).run(['update-packages', '--cherry-pick=vector_math:^2.0.9,meta:1.0.5']); expect( pub.pubspecs[flutterSdk.absolute.path]!.first.dependencies, (Pubspec.parse(kFlutterWorkspacePubspecYaml) ..dependencies['vector_math'] = HostedDependency( version: VersionConstraint.parse('^2.0.9'), ) ..dependencies['meta'] = HostedDependency( version: VersionConstraint.parse('1.0.5'), )) .dependencies, ); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), Logger: () => logger, }, ); testUsingContext( '--force-upgrade', () async { final command = UpdatePackagesCommand(verboseHelp: false); await createTestCommandRunner(command).run(['update-packages', '--force-upgrade']); }, overrides: { Pub: () => pub, FileSystem: () => fileSystem, ProcessManager: () => processManager, Cache: () => Cache.test(processManager: processManager), Logger: () => logger, }, ); }); } class _FakePub extends Fake implements Pub { _FakePub(); Map> pubspecs = >{}; @override Future interactively( List arguments, { FlutterProject? project, required PubContext context, required String command, bool touchesPackageConfig = false, PubOutputMode outputMode = PubOutputMode.all, }) async { if (project == null) { throw ArgumentError('project must not be null'); } Pubspec pubspec; if (command == 'add') { pubspec = _add(arguments, project: project); } else if (command == 'update') { pubspec = _upgrade(arguments, project: project); } else { throw ArgumentError('Unknown command'); } (pubspecs[project.directory.path] ??= []).add(pubspec); } @override Future get({ required PubContext context, required FlutterProject project, bool upgrade = false, bool offline = false, String? flutterRootOverride, bool checkUpToDate = false, bool shouldSkipThirdPartyGenerator = true, bool enforceLockfile = false, PubOutputMode outputMode = PubOutputMode.all, }) async { (pubspecs[project.directory.path] ??= []).add( Pubspec.parse(project.pubspecFile.readAsStringSync()), ); } Pubspec _add(List arguments, {required FlutterProject project}) { final List split = arguments.first.split(':'); final String packageName = split[0]; final String packageVersion = split[1]; final pubspec = Pubspec.parse(project.pubspecFile.readAsStringSync()); pubspec.dependencies[packageName] = HostedDependency( version: VersionConstraint.parse(packageVersion), ); return pubspec; } Pubspec _upgrade(List arguments, {required FlutterProject project}) { final String pubspec = project.pubspecFile.readAsStringSync(); project.pubspecFile.writeAsStringSync( pubspec .replaceFirst('typed_data: ^1.1.6', 'typed_data: ^1.1.1') .replaceFirst('unified_analytics: ^8.0.5', 'unified_analytics: ^8.0.10'), ); final upgradedPubspec = Pubspec.parse(project.pubspecFile.readAsStringSync()); for (final MapEntry(key: packageName, value: dependency) in [ ...upgradedPubspec.dependencies.entries, ...upgradedPubspec.devDependencies.entries, ]) { if (dependency is! PathDependency) { continue; } // Verify that projects with path dependencies can actually find the pubspec.yaml at the // path. // // Regression test for https://github.com/flutter/flutter/issues/179941. final FlutterProject targetProject = FlutterProject.fromDirectoryTest( project.directory.childDirectory(dependency.path), ); expect(targetProject.pubspecFile, exists); expect(targetProject.manifest.appName, packageName); } return upgradedPubspec; } }