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