1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:args/args.dart';
6import 'package:unified_analytics/unified_analytics.dart';
7
8import '../base/common.dart';
9import '../base/os.dart';
10import '../base/utils.dart';
11import '../build_info.dart';
12import '../build_system/build_system.dart';
13import '../build_system/targets/localizations.dart';
14import '../cache.dart';
15import '../dart/generate_synthetic_packages.dart';
16import '../dart/package_map.dart';
17import '../dart/pub.dart';
18import '../flutter_plugins.dart';
19import '../globals.dart' as globals;
20import '../plugins.dart';
21import '../project.dart';
22import '../reporting/reporting.dart';
23import '../runner/flutter_command.dart';
24
25/// The function signature of the [print] function.
26typedef PrintFn = void Function(Object?);
27
28class PackagesCommand extends FlutterCommand {
29 PackagesCommand({
30 PrintFn usagePrintFn = print,
31 }) : _usagePrintFn = usagePrintFn
32 {
33 addSubcommand(PackagesGetCommand('get', "Get the current package's dependencies.", PubContext.pubGet));
34 addSubcommand(PackagesGetCommand('upgrade', "Upgrade the current package's dependencies to latest versions.", PubContext.pubUpgrade));
35 addSubcommand(PackagesGetCommand('add', 'Add a dependency to pubspec.yaml.', PubContext.pubAdd));
36 addSubcommand(PackagesGetCommand('remove', 'Removes a dependency from the current package.', PubContext.pubRemove));
37 addSubcommand(PackagesTestCommand());
38 addSubcommand(PackagesForwardCommand('publish', 'Publish the current package to pub.dartlang.org.', requiresPubspec: true));
39 addSubcommand(PackagesForwardCommand('downgrade', 'Downgrade packages in a Flutter project.', requiresPubspec: true));
40 addSubcommand(PackagesForwardCommand('deps', 'Print package dependencies.')); // path to package can be specified with --directory argument
41 addSubcommand(PackagesForwardCommand('run', 'Run an executable from a package.', requiresPubspec: true));
42 addSubcommand(PackagesForwardCommand('cache', 'Work with the Pub system cache.'));
43 addSubcommand(PackagesForwardCommand('version', 'Print Pub version.'));
44 addSubcommand(PackagesForwardCommand('uploader', 'Manage uploaders for a package on pub.dev.'));
45 addSubcommand(PackagesForwardCommand('login', 'Log into pub.dev.'));
46 addSubcommand(PackagesForwardCommand('logout', 'Log out of pub.dev.'));
47 addSubcommand(PackagesForwardCommand('global', 'Work with Pub global packages.'));
48 addSubcommand(PackagesForwardCommand('outdated', 'Analyze dependencies to find which ones can be upgraded.', requiresPubspec: true));
49 addSubcommand(PackagesForwardCommand('token', 'Manage authentication tokens for hosted pub repositories.'));
50 addSubcommand(PackagesPassthroughCommand());
51 }
52
53 final PrintFn _usagePrintFn;
54
55 @override
56 final String name = 'pub';
57
58 @override
59 List<String> get aliases => const <String>['packages'];
60
61 @override
62 final String description = 'Commands for managing Flutter packages.';
63
64 @override
65 String get category => FlutterCommandCategory.project;
66
67 @override
68 Future<FlutterCommandResult> runCommand() async => FlutterCommandResult.fail();
69
70 @override
71 void printUsage() => _usagePrintFn(usage);
72}
73
74class PackagesTestCommand extends FlutterCommand {
75 PackagesTestCommand() {
76 requiresPubspecYaml();
77 }
78
79 @override
80 String get name => 'test';
81
82 @override
83 String get description {
84 return 'Run the "test" package.\n'
85 'This is similar to "flutter test", but instead of hosting the tests in the '
86 'flutter environment it hosts the tests in a pure Dart environment. The main '
87 'differences are that the "dart:ui" library is not available and that tests '
88 'run faster. This is helpful for testing libraries that do not depend on any '
89 'packages from the Flutter SDK. It is equivalent to "pub run test".';
90 }
91
92 @override
93 String get invocation {
94 return '${runner!.executableName} pub test [<tests...>]';
95 }
96
97 @override
98 Future<FlutterCommandResult> runCommand() async {
99 await pub.batch(<String>['run', 'test', ...argResults!.rest], context: PubContext.runTest);
100 return FlutterCommandResult.success();
101 }
102}
103
104class PackagesForwardCommand extends FlutterCommand {
105 PackagesForwardCommand(this._commandName, this._description, {bool requiresPubspec = false}) {
106 if (requiresPubspec) {
107 requiresPubspecYaml();
108 }
109 }
110
111 PubContext context = PubContext.pubForward;
112
113 @override
114 ArgParser argParser = ArgParser.allowAnything();
115
116 final String _commandName;
117 final String _description;
118
119 @override
120 String get name => _commandName;
121
122 @override
123 String get description {
124 return '$_description\n'
125 'This runs the "pub" tool in a Flutter context.';
126 }
127
128 @override
129 String get invocation {
130 return '${runner!.executableName} pub $_commandName [<arguments...>]';
131 }
132
133 @override
134 Future<FlutterCommandResult> runCommand() async {
135 final List<String> subArgs = argResults!.rest.toList()
136 ..removeWhere((String arg) => arg == '--');
137 await pub.interactively(
138 <String>[ _commandName, ...subArgs],
139 context: context,
140 command: _commandName,
141 );
142 return FlutterCommandResult.success();
143 }
144}
145
146class PackagesPassthroughCommand extends FlutterCommand {
147 @override
148 ArgParser argParser = ArgParser.allowAnything();
149
150 @override
151 String get name => 'pub';
152
153 @override
154 String get description {
155 return 'Pass the remaining arguments to Dart\'s "pub" tool.\n'
156 'This runs the "pub" tool in a Flutter context.';
157 }
158
159 @override
160 String get invocation {
161 return '${runner!.executableName} packages pub [<arguments...>]';
162 }
163
164 static final PubContext _context = PubContext.pubPassThrough;
165
166 @override
167 Future<FlutterCommandResult> runCommand() async {
168 await pub.interactively(
169 command: 'pub',
170 argResults!.rest,
171 context: _context,
172 );
173 return FlutterCommandResult.success();
174 }
175}
176
177/// Represents the pub sub-commands that makes package-resolutions.
178class PackagesGetCommand extends FlutterCommand {
179 PackagesGetCommand(this._commandName, this._description, this._context);
180
181 @override
182 ArgParser argParser = ArgParser.allowAnything();
183
184 final String _commandName;
185 final String _description;
186 final PubContext _context;
187
188 FlutterProject? _rootProject;
189
190 @override
191 String get name => _commandName;
192
193 @override
194 String get description {
195 return '$_description\n'
196 'This runs the "pub" tool in a Flutter context.';
197 }
198
199 @override
200 String get invocation {
201 return '${runner!.executableName} pub $_commandName [<arguments...>]';
202 }
203
204 /// An [ArgParser] that accepts all options and flags that the
205 ///
206 /// `pub get`
207 /// `pub upgrade`
208 /// `pub downgrade`
209 /// `pub add`
210 /// `pub remove`
211 ///
212 /// commands accept.
213 ArgParser get _permissiveArgParser {
214 final ArgParser argParser = ArgParser();
215 argParser.addOption('directory', abbr: 'C');
216 argParser.addFlag('offline');
217 argParser.addFlag('dry-run', abbr: 'n');
218 argParser.addFlag('help', abbr: 'h');
219 argParser.addFlag('enforce-lockfile');
220 argParser.addFlag('precompile');
221 argParser.addFlag('major-versions');
222 argParser.addFlag('null-safety');
223 argParser.addFlag('example', defaultsTo: true);
224 argParser.addOption('sdk');
225 argParser.addOption('path');
226 argParser.addOption('hosted-url');
227 argParser.addOption('git-url');
228 argParser.addOption('git-ref');
229 argParser.addOption('git-path');
230 argParser.addFlag('dev');
231 argParser.addFlag('verbose', abbr: 'v');
232 return argParser;
233 }
234
235 @override
236 Future<FlutterCommandResult> runCommand() async {
237 List<String> rest = argResults!.rest;
238 bool isHelp = false;
239 bool example = true;
240 bool exampleWasParsed = false;
241 String? directoryOption;
242 bool dryRun = false;
243 try {
244 final ArgResults results = _permissiveArgParser.parse(rest);
245 isHelp = results['help'] as bool;
246 directoryOption = results['directory'] as String?;
247 example = results['example'] as bool;
248 exampleWasParsed = results.wasParsed('example');
249 dryRun = results['dry-run'] as bool;
250 } on ArgParserException {
251 // Let pub give the error message.
252 }
253 String? target;
254 FlutterProject? rootProject;
255
256 if (!isHelp) {
257 if (directoryOption == null &&
258 rest.length == 1 &&
259 // Anything that looks like an argument should not be interpreted as
260 // a directory.
261 !rest.single.startsWith('-') &&
262 ((rest.single.contains('/') || rest.single.contains(r'\')) ||
263 name == 'get')) {
264 // For historical reasons, if there is one argument to the command and it contains
265 // a multiple-component path (i.e. contains a slash) then we use that to determine
266 // to which project we're applying the command.
267 target = findProjectRoot(globals.fs, rest.single);
268
269 globals.printWarning('''
270 Using a naked argument for directory is deprecated and will stop working in a future Flutter release.
271
272 Use --directory instead.''');
273 if (target == null) {
274 throwToolExit('Expected to find project root in ${rest.single}.');
275 }
276 rest = <String>[];
277 } else {
278 target = findProjectRoot(globals.fs, directoryOption);
279 if (target == null) {
280 if (directoryOption == null) {
281 throwToolExit('Expected to find project root in current working directory.');
282 } else {
283 throwToolExit('Expected to find project root in $directoryOption.');
284 }
285 }
286 }
287
288 rootProject = FlutterProject.fromDirectory(globals.fs.directory(target));
289 _rootProject = rootProject;
290
291 if (rootProject.manifest.generateSyntheticPackage) {
292 final Environment environment = Environment(
293 artifacts: globals.artifacts!,
294 logger: globals.logger,
295 cacheDir: globals.cache.getRoot(),
296 engineVersion: globals.flutterVersion.engineRevision,
297 fileSystem: globals.fs,
298 flutterRootDir: globals.fs.directory(Cache.flutterRoot),
299 outputDir: globals.fs.directory(getBuildDirectory()),
300 processManager: globals.processManager,
301 platform: globals.platform,
302 usage: globals.flutterUsage,
303 analytics: analytics,
304 projectDir: rootProject.directory,
305 packageConfigPath: packageConfigPath(),
306 generateDartPluginRegistry: true,
307 );
308
309 await generateLocalizationsSyntheticPackage(
310 environment: environment,
311 buildSystem: globals.buildSystem,
312 buildTargets: globals.buildTargets,
313 );
314 } else if (rootProject.directory.childFile('l10n.yaml').existsSync()) {
315 final Environment environment = Environment(
316 artifacts: globals.artifacts!,
317 logger: globals.logger,
318 cacheDir: globals.cache.getRoot(),
319 engineVersion: globals.flutterVersion.engineRevision,
320 fileSystem: globals.fs,
321 flutterRootDir: globals.fs.directory(Cache.flutterRoot),
322 outputDir: globals.fs.directory(getBuildDirectory()),
323 processManager: globals.processManager,
324 platform: globals.platform,
325 usage: globals.flutterUsage,
326 analytics: analytics,
327 projectDir: rootProject.directory,
328 packageConfigPath: packageConfigPath(),
329 generateDartPluginRegistry: true,
330 );
331 final BuildResult result = await globals.buildSystem.build(
332 const GenerateLocalizationsTarget(),
333 environment,
334 );
335 if (result.hasException) {
336 throwToolExit(
337 'Generating synthetic localizations package failed with ${result.exceptions.length} ${pluralize('error', result.exceptions.length)}:'
338 '\n\n'
339 '${result.exceptions.values.map<Object?>((ExceptionMeasurement e) => e.exception).join('\n\n')}',
340 );
341 }
342 }
343 }
344 final String? relativeTarget = target == null ? null : globals.fs.path.relative(target);
345
346 final List<String> subArgs = rest.toList()..removeWhere((String arg) => arg == '--');
347 final Stopwatch timer = Stopwatch()..start();
348 try {
349 await pub.interactively(
350 <String>[
351 name,
352 ...subArgs,
353 // `dart pub get` and friends defaults to `--no-example`.
354 if (!exampleWasParsed && target != null) '--example',
355 if (directoryOption == null && relativeTarget != null) ...<String>['--directory', relativeTarget],
356 ],
357 project: rootProject,
358 context: _context,
359 command: name,
360 touchesPackageConfig: !(isHelp || dryRun),
361 );
362 final Duration elapsedDuration = timer.elapsed;
363 globals.flutterUsage.sendTiming('pub', 'get', elapsedDuration, label: 'success');
364 analytics.send(Event.timing(
365 workflow: 'pub',
366 variableName: 'get',
367 elapsedMilliseconds: elapsedDuration.inMilliseconds,
368 label: 'success'
369 ));
370 // Not limiting to catching Exception because the exception is rethrown.
371 } catch (_) { // ignore: avoid_catches_without_on_clauses
372 final Duration elapsedDuration = timer.elapsed;
373 globals.flutterUsage.sendTiming('pub', 'get', elapsedDuration, label: 'failure');
374 analytics.send(Event.timing(
375 workflow: 'pub',
376 variableName: 'get',
377 elapsedMilliseconds: elapsedDuration.inMilliseconds,
378 label: 'failure'
379 ));
380 rethrow;
381 }
382
383 if (rootProject != null) {
384 // We need to regenerate the platform specific tooling for both the project
385 // itself and example(if present).
386 await rootProject.regeneratePlatformSpecificTooling();
387 if (example && rootProject.hasExampleApp && rootProject.example.pubspecFile.existsSync()) {
388 final FlutterProject exampleProject = rootProject.example;
389 await exampleProject.regeneratePlatformSpecificTooling();
390 }
391 }
392
393 return FlutterCommandResult.success();
394 }
395
396 late final Future<List<Plugin>> _pluginsFound = (() async {
397 final FlutterProject? rootProject = _rootProject;
398 if (rootProject == null) {
399 return <Plugin>[];
400 }
401
402 return findPlugins(rootProject, throwOnError: false);
403 })();
404
405 late final String? _androidEmbeddingVersion = _rootProject?.android.getEmbeddingVersion().toString().split('.').last;
406
407 /// The pub packages usage values are incorrect since these are calculated/sent
408 /// before pub get completes. This needs to be performed after dependency resolution.
409 @override
410 Future<CustomDimensions> get usageValues async {
411 final FlutterProject? rootProject = _rootProject;
412 if (rootProject == null) {
413 return const CustomDimensions();
414 }
415
416 int numberPlugins;
417 // Do not send plugin analytics if pub has not run before.
418 final bool hasPlugins = rootProject.flutterPluginsDependenciesFile.existsSync()
419 && findPackageConfigFile(rootProject.directory) != null;
420 if (hasPlugins) {
421 // Do not fail pub get if package config files are invalid before pub has
422 // had a chance to run.
423 final List<Plugin> plugins = await _pluginsFound;
424 numberPlugins = plugins.length;
425 } else {
426 numberPlugins = 0;
427 }
428
429 return CustomDimensions(
430 commandPackagesNumberPlugins: numberPlugins,
431 commandPackagesProjectModule: rootProject.isModule,
432 commandPackagesAndroidEmbeddingVersion: _androidEmbeddingVersion,
433 );
434 }
435
436 /// The pub packages usage values are incorrect since these are calculated/sent
437 /// before pub get completes. This needs to be performed after dependency resolution.
438 @override
439 Future<Event> unifiedAnalyticsUsageValues(String commandPath) async {
440 final FlutterProject? rootProject = _rootProject;
441 if (rootProject == null) {
442 return Event.commandUsageValues(workflow: commandPath, commandHasTerminal: hasTerminal);
443 }
444
445 final int numberPlugins;
446 // Do not send plugin analytics if pub has not run before.
447 final bool hasPlugins = rootProject.flutterPluginsDependenciesFile.existsSync()
448 && findPackageConfigFile(rootProject.directory) != null;
449 if (hasPlugins) {
450 // Do not fail pub get if package config files are invalid before pub has
451 // had a chance to run.
452 final List<Plugin> plugins = await _pluginsFound;
453 numberPlugins = plugins.length;
454 } else {
455 numberPlugins = 0;
456 }
457
458 return Event.commandUsageValues(
459 workflow: commandPath,
460 commandHasTerminal: hasTerminal,
461 packagesNumberPlugins: numberPlugins,
462 packagesProjectModule: rootProject.isModule,
463 packagesAndroidEmbeddingVersion: _androidEmbeddingVersion,
464 );
465 }
466}
467

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com