diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 6422d39d72..859b97ceb6 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -74,6 +74,9 @@ Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { buildDir: buildDirectory, sourceDir: windowsProject.cmakeFile.parent, ); + if (visualStudio.displayVersion == '17.1.0') { + _fixBrokenCmakeGeneration(buildDirectory); + } await _runBuild(cmakePath, buildDirectory, buildModeName); } finally { status.cancel(); @@ -335,3 +338,55 @@ void _writeGeneratedFlutterConfig( } writeGeneratedCmakeConfig(Cache.flutterRoot!, windowsProject, environment); } + +// Works around the Visual Studio 17.1.0 CMake bug described in +// https://github.com/flutter/flutter/issues/97086 +// +// Rather than attempt to remove all the duplicate entries within the +// element, which would require a more complicated parser, this +// just fixes the incorrect duplicates to have the correct `$` value, +// making the duplication harmless. +// +// TODO(stuartmorgan): Remove this workaround either once 17.1.0 is +// sufficiently old that we no longer need to support it, or when +// dropping VS 2022 support. +void _fixBrokenCmakeGeneration(Directory buildDirectory) { + final File assembleProject = buildDirectory + .childDirectory('flutter') + .childFile('flutter_assemble.vcxproj'); + if (assembleProject.existsSync()) { + // E.g.: + final RegExp commandRegex = RegExp( + r' TestFeatureFlags(isWindowsEnabled: true), }); + testUsingContext('Windows build works around CMake generation bug', () async { + final FakeVisualStudio fakeVisualStudio = FakeVisualStudio(displayVersion: '17.1.0'); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = fakeVisualStudio; + setUpMockProjectFilesForBuild(); + + processManager = FakeProcessManager.list([ + cmakeGenerationCommand(), + buildCommand('Release'), + ]); + fileSystem.file(fileSystem.path.join('lib', 'other.dart')) + .createSync(recursive: true); + fileSystem.file(fileSystem.path.join('foo', 'bar.sksl.json')) + .createSync(recursive: true); + + // Relevant portions of an incorrectly generated project, with some + // irrelevant details removed for length. + const String fakeBadProjectContent = r''' + + + + + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Debug +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Debug +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Debug +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Profile +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Profile +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Profile +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Release +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Release +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + Generating some files + setlocal +"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64 Release +endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone +:cmErrorLevel +exit /b %1 +:cmDone +if %errorlevel% neq 0 goto :VCEnd + + + +'''; + final File assembleProject = fileSystem.currentDirectory + .childDirectory('build') + .childDirectory('windows') + .childDirectory('flutter') + .childFile('flutter_assemble.vcxproj'); + assembleProject.createSync(recursive: true); + assembleProject.writeAsStringSync(fakeBadProjectContent); + + await createTestCommandRunner(command).run( + const ['windows', '--no-pub'] + ); + + final List projectLines = assembleProject.readAsLinesSync(); + + const String commandBase = r'"C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" ' + r'-E env FOO=bar C:/src/flutter/packages/flutter_tools/bin/tool_backend.bat windows-x64'; + // The duplicate commands will still be present, but with the order matching + // the condition order (cycling through the configurations), rather than + // three copies of Debug, then three copies of Profile, then three copies + // of Release. + expect(projectLines, containsAllInOrder([ + '$commandBase Debug\r', + '$commandBase Profile\r', + '$commandBase Release\r', + '$commandBase Debug\r', + '$commandBase Profile\r', + '$commandBase Release\r', + '$commandBase Debug\r', + '$commandBase Profile\r', + '$commandBase Release\r', + ])); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Platform: () => windowsPlatform, + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); + testUsingContext('Windows build invokes build and writes generated files', () async { final FakeVisualStudio fakeVisualStudio = FakeVisualStudio(); final BuildWindowsCommand command = BuildWindowsCommand() @@ -604,6 +740,7 @@ class FakeVisualStudio extends Fake implements VisualStudio { FakeVisualStudio({ this.cmakePath = _cmakePath, this.cmakeGenerator = 'Visual Studio 16 2019', + this.displayVersion = '17.0.0' }); @override @@ -611,4 +748,7 @@ class FakeVisualStudio extends Fake implements VisualStudio { @override final String cmakeGenerator; + + @override + final String displayVersion; }