Unverified Commit 80660b2d authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Work around VS CMake generation bug (#98945)

parent 35df3aa4
......@@ -74,6 +74,9 @@ Future<void> 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
// <CustomBuild> element, which would require a more complicated parser, this
// just fixes the incorrect duplicates to have the correct `$<CONFIG>` 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.: <Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
final RegExp commandRegex = RegExp(
r'<Command Condition=.*\(Configuration\)\|\$\(Platform\).==.(Debug|Profile|Release)\|');
// E.g.: [...]/flutter_tools/bin/tool_backend.bat windows-x64 Debug
final RegExp assembleCallRegex = RegExp(
r'^.*/tool_backend\.bat windows[^ ]* (Debug|Profile|Release)');
String? lastCommandConditionConfig;
final StringBuffer newProjectContents = StringBuffer();
// vcxproj files contain a BOM, which readAsLinesSync drops; re-add it.
newProjectContents.writeCharCode(unicodeBomCharacterRune);
for (final String line in assembleProject.readAsLinesSync()) {
final RegExpMatch? commandMatch = commandRegex.firstMatch(line);
if (commandMatch != null) {
lastCommandConditionConfig = commandMatch.group(1);
} else if (lastCommandConditionConfig != null) {
final RegExpMatch? assembleCallMatch = assembleCallRegex.firstMatch(line);
if (assembleCallMatch != null) {
final String callConfig = assembleCallMatch.group(1)!;
if (callConfig != lastCommandConditionConfig) {
// The config is the end of the line; make sure to replace that one,
// in case config-matching strings appear anywhere else in the line
// (e.g., the project path).
final int badConfigIndex = line.lastIndexOf(assembleCallMatch.group(1)!);
final String correctedLine = line.replaceFirst(
callConfig, lastCommandConditionConfig, badConfigIndex);
newProjectContents.writeln('$correctedLine\r');
continue;
}
}
}
newProjectContents.writeln('$line\r');
}
assembleProject.writeAsStringSync(newProjectContents.toString());
}
}
......@@ -309,6 +309,142 @@ C:\foo\windows\runner\main.cpp(17,1): error C2065: 'Baz': undeclared identifier
FeatureFlags: () => 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(<FakeCommand>[
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'''
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<CustomBuild Include="somepath\build\windows\CMakeFiles\8b570225f626c250e12bc1ede88babae\flutter_windows.dll.rule">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generating some files</Message>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd</Command>
</CustomBuild>
</ItemGroup>
</Project>
''';
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 <String>['windows', '--no-pub']
);
final List<String> 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(<String>[
'$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: <Type, Generator>{
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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment