diff --git a/.ci/scripts/analyze_pathified.sh b/.ci/scripts/analyze_pathified.sh index 2942cf7d57b81..01c4bd8cc17f5 100755 --- a/.ci/scripts/analyze_pathified.sh +++ b/.ci/scripts/analyze_pathified.sh @@ -10,7 +10,13 @@ set -e # This uses --run-on-dirty-packages rather than --packages-for-branch # since only the packages changed by 'make-deps-path-based' need to be # re-checked. +# --skip-if-resolving-fails is used to avoid failing if there's a resolution +# conflict when using path-based dependencies, because that indicates that +# the failing packages won't pick up the new versions of the changed packages +# when they are published anyway, so publishing won't cause an out-of-band +# failure regardless. dart ./script/tool/bin/flutter_plugin_tools.dart analyze --run-on-dirty-packages \ + --skip-if-resolving-fails \ --log-timing --custom-analysis=script/configs/custom_analysis.yaml # Restore the tree to a clean state, to avoid accidental issues if # other script steps are added to the enclosing task. diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart index 225e60cbc3d70..15a8ef2e125b1 100644 --- a/script/tool/lib/src/analyze_command.dart +++ b/script/tool/lib/src/analyze_command.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io' as io; + import 'package:file/file.dart'; import 'package:yaml/yaml.dart'; @@ -34,12 +36,18 @@ class AnalyzeCommand extends PackageLoopingCommand { argParser.addFlag(_libOnlyFlag, help: 'Only analyze the lib/ directory of the main package, not the ' 'entire package.'); + argParser.addFlag(_skipIfResolvingFailsFlag, + help: 'If resolution fails, skip the package. This is only ' + 'intended to be used with pathified analysis, where a resolver ' + 'failure indicates that no out-of-band failure can result anyway.', + hide: true); } static const String _customAnalysisFlag = 'custom-analysis'; static const String _downgradeFlag = 'downgrade'; static const String _libOnlyFlag = 'lib-only'; static const String _analysisSdk = 'analysis-sdk'; + static const String _skipIfResolvingFailsFlag = 'skip-if-resolving-fails'; late String _dartBinaryPath; @@ -134,6 +142,20 @@ class AnalyzeCommand extends PackageLoopingCommand { .pubspecFile .existsSync()) { if (!await _runPubCommand(packageToGet, 'get')) { + if (getBoolArg(_skipIfResolvingFailsFlag)) { + // Re-run, capturing output, to see if the failure was a resolver + // failure. (This is slightly inefficient, but this should be a + // very rare case.) + const String resolverFailureMessage = 'version solving failed'; + final io.ProcessResult result = await processRunner.run( + flutterCommand, ['pub', 'get'], + workingDir: packageToGet.directory); + if ((result.stderr as String).contains(resolverFailureMessage) || + (result.stdout as String).contains(resolverFailureMessage)) { + logWarning('Skipping package due to pub resolution failure.'); + return PackageResult.skip('Resolution failed.'); + } + } return PackageResult.fail(['Unable to get dependencies']); } } diff --git a/script/tool/test/analyze_command_test.dart b/script/tool/test/analyze_command_test.dart index 37f3332d83a48..15da8db22cfb3 100644 --- a/script/tool/test/analyze_command_test.dart +++ b/script/tool/test/analyze_command_test.dart @@ -315,6 +315,39 @@ void main() { }); }); + test('skips if requested if "pub get" fails in the resolver', () async { + final RepositoryPackage plugin = createFakePlugin('foo', packagesDir); + + final FakeProcessInfo failingPubGet = FakeProcessInfo( + MockProcess( + exitCode: 1, + stderr: 'So, because foo depends on both thing_one ^1.0.0 and ' + 'thing_two from path, version solving failed.'), + ['pub', 'get']); + processRunner.mockProcessesForExecutable['flutter'] = [ + failingPubGet, + // The command re-runs failures when --skip-if-resolver-fails is passed + // to check the output, so provide the same failing outcome. + failingPubGet, + ]; + + final List output = await runCapturingPrint( + runner, ['analyze', '--skip-if-resolving-fails']); + + expect( + output, + containsAllInOrder([ + contains('Skipping package due to pub resolution failure.'), + ]), + ); + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall('flutter', const ['pub', 'get'], plugin.path), + ProcessCall('flutter', const ['pub', 'get'], plugin.path), + ])); + }); + test('fails if "pub get" fails', () async { createFakePlugin('foo', packagesDir);