diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart index a2965d2474..d7691849cc 100644 --- a/packages/flutter_tools/lib/src/base/process.dart +++ b/packages/flutter_tools/lib/src/base/process.dart @@ -257,6 +257,9 @@ abstract class ProcessUtils { /// If [filter] is non-null, all lines that do not match it are removed. If /// [mapFunction] is present, all lines that match [filter] are also forwarded /// to [mapFunction] for further processing. + /// + /// If [stdoutErrorMatcher] is non-null, matching lines from stdout will be + /// treated as errors, just as if they had been logged to stderr instead. Future stream( List cmd, { String workingDirectory, @@ -264,6 +267,7 @@ abstract class ProcessUtils { String prefix = '', bool trace = false, RegExp filter, + RegExp stdoutErrorMatcher, StringConverter mapFunction, Map environment, }); @@ -485,6 +489,7 @@ class _DefaultProcessUtils implements ProcessUtils { String prefix = '', bool trace = false, RegExp filter, + RegExp stdoutErrorMatcher, StringConverter mapFunction, Map environment, }) async { @@ -504,7 +509,9 @@ class _DefaultProcessUtils implements ProcessUtils { } if (line != null) { final String message = '$prefix$line'; - if (trace) { + if (stdoutErrorMatcher?.hasMatch(line) == true) { + _logger.printError(message, wrap: false); + } else if (trace) { _logger.printTrace(message); } else { _logger.printStatus(message, wrap: false); diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 31a9967eec..b4b4cd36e9 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -107,6 +107,10 @@ Future _runCmakeGeneration(String cmakePath, Directory buildDir, Directory Future _runBuild(String cmakePath, Directory buildDir, String buildModeName) async { final Stopwatch sw = Stopwatch()..start(); + // MSBuild sends all output to stdout, including build errors. This surfaces + // known error patterns. + final RegExp errorMatcher = RegExp(r':\s*(?:warning|(?:fatal )?error).*?:'); + int result; try { result = await processUtils.stream( @@ -126,13 +130,13 @@ Future _runBuild(String cmakePath, Directory buildDir, String buildModeNam 'VERBOSE_SCRIPT_LOGGING': 'true' }, trace: true, + stdoutErrorMatcher: errorMatcher, ); } on ArgumentError { throwToolExit("cmake not found. Run 'flutter doctor' for more information."); } if (result != 0) { - final String verboseInstructions = globals.logger.isVerbose ? '' : ' To view the stack trace, please run `flutter run -d windows -v`.'; - throwToolExit('Build process failed.$verboseInstructions'); + throwToolExit('Build process failed.'); } globals.flutterUsage.sendTiming('build', 'windows-cmake-build', Duration(milliseconds: sw.elapsedMilliseconds)); } diff --git a/packages/flutter_tools/templates/app/windows.tmpl/flutter/CMakeLists.txt b/packages/flutter_tools/templates/app/windows.tmpl/flutter/CMakeLists.txt index 98bb564622..ff47d32742 100644 --- a/packages/flutter_tools/templates/app/windows.tmpl/flutter/CMakeLists.txt +++ b/packages/flutter_tools/templates/app/windows.tmpl/flutter/CMakeLists.txt @@ -80,11 +80,13 @@ add_dependencies(flutter_wrapper_app flutter_assemble) # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index e6fc64cef2..a03bb23822 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -239,6 +239,63 @@ void main() { FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); + testUsingContext('Windows build extracts errors from stdout', () async { + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; + applyMocksToCommand(command); + setUpMockProjectFilesForBuild(); + when(mockVisualStudio.cmakePath).thenReturn(cmakePath); + + // This contains a mix of routine build output and various types of errors + // (compile error, link error, warning treated as an error) from MSBuild, + // edited down for compactness. For instance, where similar lines are + // repeated in actual output, one or two representative lines are chosen + // to be included here. + const String stdout = r'''Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Framework +Copyright (C) Microsoft Corporation. All rights reserved. + + Checking Build System + Generating C:/foo/windows/flutter/ephemeral/flutter_windows.dll, [etc], _phony_ + Building Custom Rule C:/foo/windows/flutter/CMakeLists.txt + standard_codec.cc + Generating Code... + flutter_wrapper_plugin.vcxproj -> C:\foo\build\windows\flutter\Debug\flutter_wrapper_plugin.lib +C:\foo\windows\runner\main.cpp(18): error C2220: the following warning is treated as an error [C:\foo\build\windows\runner\test.vcxproj] +C:\foo\windows\runner\main.cpp(18): warning C4706: assignment within conditional expression [C:\foo\build\windows\runner\test.vcxproj] +main.obj : error LNK2019: unresolved external symbol "void __cdecl Bar(void)" (?Bar@@YAXXZ) referenced in function wWinMain [C:\foo\build\windows\runner\test.vcxproj] +C:\foo\build\windows\runner\Debug\test.exe : fatal error LNK1120: 1 unresolved externals [C:\foo\build\windows\runner\test.vcxproj] + Building Custom Rule C:/foo/windows/runner/CMakeLists.txt + flutter_window.cpp + main.cpp +C:\foo\windows\runner\main.cpp(17,1): error C2065: 'Baz': undeclared identifier [C:\foo\build\windows\runner\test.vcxproj] + -- Install configuration: "Debug" + -- Installing: C:/foo/build/windows/runner/Debug/data/icudtl.dat +'''; + + processManager = FakeProcessManager.list([ + cmakeGenerationCommand(), + buildCommand('Release', + stdout: stdout, + ), + ]); + + await createTestCommandRunner(command).run( + const ['windows', '--no-pub'] + ); + // Just the warnings and errors should be surfaced. + expect(testLogger.errorText, r'''C:\foo\windows\runner\main.cpp(18): error C2220: the following warning is treated as an error [C:\foo\build\windows\runner\test.vcxproj] +C:\foo\windows\runner\main.cpp(18): warning C4706: assignment within conditional expression [C:\foo\build\windows\runner\test.vcxproj] +main.obj : error LNK2019: unresolved external symbol "void __cdecl Bar(void)" (?Bar@@YAXXZ) referenced in function wWinMain [C:\foo\build\windows\runner\test.vcxproj] +C:\foo\build\windows\runner\Debug\test.exe : fatal error LNK1120: 1 unresolved externals [C:\foo\build\windows\runner\test.vcxproj] +C:\foo\windows\runner\main.cpp(17,1): error C2065: 'Baz': undeclared identifier [C:\foo\build\windows\runner\test.vcxproj] +'''); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Platform: () => windowsPlatform, + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); + testUsingContext('Windows verbose build sets VERBOSE_SCRIPT_LOGGING', () async { final BuildWindowsCommand command = BuildWindowsCommand() ..visualStudioOverride = mockVisualStudio;