diff --git a/tools/engine_tool/lib/src/commands/query_command.dart b/tools/engine_tool/lib/src/commands/query_command.dart index 37ed01031a290..c22f61803047e 100644 --- a/tools/engine_tool/lib/src/commands/query_command.dart +++ b/tools/engine_tool/lib/src/commands/query_command.dart @@ -221,7 +221,8 @@ et query targets //flutter/fml/... # List all targets under `//flutter/fml` } if (allTargets.isEmpty) { - environment.logger.fatal('Query unexpectedly returned an empty list'); + environment.logger.error('No targets found, nothing to query.'); + return 1; } for (final BuildTarget target in allTargets) { diff --git a/tools/engine_tool/lib/src/commands/test_command.dart b/tools/engine_tool/lib/src/commands/test_command.dart index a48e10efec093..3c3b656774851 100644 --- a/tools/engine_tool/lib/src/commands/test_command.dart +++ b/tools/engine_tool/lib/src/commands/test_command.dart @@ -86,6 +86,11 @@ et test //flutter/fml:fml_benchmarks # Run a single test target in `//flutter/f buildTargets.addAll(found); } + if (buildTargets.isEmpty) { + environment.logger.error('No targets found, nothing to test.'); + return 1; + } + // Make sure there is at least one test target. final List testTargets = buildTargets .whereType() diff --git a/tools/engine_tool/lib/src/gn.dart b/tools/engine_tool/lib/src/gn.dart index a1a29bbcd16ab..d203ee685af80 100644 --- a/tools/engine_tool/lib/src/gn.dart +++ b/tools/engine_tool/lib/src/gn.dart @@ -55,6 +55,26 @@ interface class Gn { failOk: true, ); if (process.exitCode != 0) { + // If the error was in the format: + // "The input testing/scenario_app:scenario_app matches no targets, configs or files." + // + // Then report a nicer error, versus a fatal error. + final stdout = process.stdout; + if (stdout.contains('matches no targets, configs or files')) { + final gnPattern = pattern.toGnPattern(); + if (!gnPattern.startsWith('//flutter')) { + _environment.logger.warning( + 'No targets matched the pattern `$gnPattern`.' + 'Did you mean `//flutter/$gnPattern`?', + ); + } else { + _environment.logger.warning( + 'No targets matched the pattern `${pattern.toGnPattern()}`', + ); + } + return []; + } + _environment.logger.fatal( 'Failed to run `${command.join(' ')}` (exit code ${process.exitCode})' '\n\n' diff --git a/tools/engine_tool/test/build_command_test.dart b/tools/engine_tool/test/build_command_test.dart index c8af8cc3fe68a..1344db1053b49 100644 --- a/tools/engine_tool/test/build_command_test.dart +++ b/tools/engine_tool/test/build_command_test.dart @@ -424,6 +424,36 @@ void main() { } }); + test('build command gracefully handles no matched targets', () async { + final List cannedProcesses = [ + CannedProcess((List command) => + command.contains('desc'), + stdout: fixtures.gnDescOutputEmpty(gnPattern: 'testing/scenario_app:sceario_app'), + exitCode: 1), + ]; + final TestEnvironment testEnv = TestEnvironment.withTestEngine( + cannedProcesses: cannedProcesses, + ); + try { + final ToolCommandRunner runner = ToolCommandRunner( + environment: testEnv.environment, + configs: configs, + ); + final int result = await runner.run([ + 'build', + '--config', + 'host_debug', + // Intentionally omit the prefix '//flutter/' to trigger the warning. + '//testing/scenario_app', + ]); + expect(result, equals(0)); + expect(testEnv.testLogs.map((LogRecord r) => r.message).join(), + contains('No targets matched the pattern `testing/scenario_app')); + } finally { + testEnv.cleanup(); + } + }); + test('et help build line length is not too big', () async { final List prints = []; await runZoned( diff --git a/tools/engine_tool/test/fixtures.dart b/tools/engine_tool/test/fixtures.dart index b31b24b58b688..70de4d36a94ba 100644 --- a/tools/engine_tool/test/fixtures.dart +++ b/tools/engine_tool/test/fixtures.dart @@ -319,3 +319,7 @@ String gnDescOutput() => ''' } } '''; + +String gnDescOutputEmpty({required String gnPattern}) => ''' +The input $gnPattern matches no targets, configs or files. +''';