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 | |
5 | import 'package:args/args.dart'; |
6 | import 'package:unified_analytics/unified_analytics.dart'; |
7 | |
8 | import '../base/common.dart'; |
9 | import '../base/os.dart'; |
10 | import '../base/utils.dart'; |
11 | import '../build_info.dart'; |
12 | import '../build_system/build_system.dart'; |
13 | import '../build_system/targets/localizations.dart'; |
14 | import '../cache.dart'; |
15 | import '../dart/generate_synthetic_packages.dart'; |
16 | import '../dart/package_map.dart'; |
17 | import '../dart/pub.dart'; |
18 | import '../flutter_plugins.dart'; |
19 | import '../globals.dart' as globals; |
20 | import '../plugins.dart'; |
21 | import '../project.dart'; |
22 | import '../reporting/reporting.dart'; |
23 | import '../runner/flutter_command.dart'; |
24 | |
25 | /// The function signature of the [print] function. |
26 | typedef PrintFn = void Function(Object?); |
27 | |
28 | class 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 | |
74 | class 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 | |
104 | class 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 | |
146 | class 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. |
178 | class 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 |
Definitions
- PackagesCommand
- PackagesCommand
- aliases
- category
- runCommand
- printUsage
- PackagesTestCommand
- PackagesTestCommand
- name
- description
- invocation
- runCommand
- PackagesForwardCommand
- PackagesForwardCommand
- name
- description
- invocation
- runCommand
- PackagesPassthroughCommand
- name
- description
- invocation
- runCommand
- PackagesGetCommand
- PackagesGetCommand
- name
- description
- invocation
- _permissiveArgParser
- runCommand
- usageValues
Learn more about Flutter for embedded and desktop on industrialflutter.com