Skip to content

Commit 91b386f

Browse files
committed
* Added PatchVsForLibClang to handle patching VS installs with header bug preventing parsing with LibClang
* updated Build-Interop with calls to build and run PatchVsForLibClang to allow the build to complete until an official release from MS is available.
1 parent c08fb37 commit 91b386f

File tree

10 files changed

+370
-0
lines changed

10 files changed

+370
-0
lines changed

Build-Interop.ps1

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,36 @@ try
4646
. .\buildutils.ps1
4747
$buildInfo = Initialize-BuildEnvironment -AllowVsPreReleases:$AllowVsPreReleases
4848

49+
# <HACK>
50+
# for details of why this is needed, see src\PatchVsForLibClang\readme.md
51+
# min version with fix (VS 2019 16.9 preview 2)
52+
$minOfficialVersion = [System.Version]('16.9.30803.129')
53+
54+
Write-Information "Building PatchVsForLibClang.exe"
55+
$msBuildProperties = @{ Configuration = 'Release'}
56+
$buildLogPath = Join-Path $buildInfo['BinLogsPath'] PatchVsForLibClang.binlog
57+
Invoke-MSBuild -Targets 'Restore;Build' -Project src\PatchVsForLibClang\PatchVsForLibClang.sln -Properties $msBuildProperties -LoggerArgs ($buildInfo['MsBuildLoggerArgs'] + @("/bl:$buildLogPath") )
58+
59+
$vs = Find-VSInstance -AllowVsPreReleases:$AllowVsPreReleases
60+
if($vs.InstallationVersion -lt $minOfficialVersion)
61+
{
62+
pushd (Join-Path $buildInfo.BuildOutputPath 'bin\PatchVsForLibClang\Release\netcoreapp3.1')
63+
try
64+
{
65+
Write-Information "Patching VS CRT for parsing with LibClang..."
66+
.\PatchVsForLibClang.exe $vs.InstallationPath
67+
}
68+
finally
69+
{
70+
popd
71+
}
72+
}
73+
else
74+
{
75+
Write-Information "$($vs.DisplayName) ($($vs.InstallationVersion)) already includes the official patch - skipping manual patch"
76+
}
77+
#</HACK>
78+
4979
# Download and unpack the LLVM libs if not already present, this doesn't use NuGet as the NuGet compression
5080
# is insufficient to keep the size reasonable enough to support posting to public galleries. Additionally, the
5181
# support for native lib projects in NuGet is tenuous at best. Due to various compiler version dependencies
@@ -69,6 +99,8 @@ try
6999
# Hopefully they will support .NET Core soon, if not, the generation stage may need to move out
70100
# to a manual step with the results checked in.
71101
Write-Information "Generating P/Invoke Bindings"
102+
Write-Information "LlvmBindingsGenerator.exe $($buildInfo['LlvmLibsRoot']) $(Join-Path $buildInfo['SrcRootPath'] 'Interop\LibLLVM') $(Join-Path $buildInfo['SrcRootPath'] 'Interop\Ubiquity.NET.Llvm.Interop')"
103+
72104
& "$($buildInfo['BuildOutputPath'])\bin\LlvmBindingsGenerator\Release\net47\LlvmBindingsGenerator.exe" $buildInfo['LlvmLibsRoot'] (Join-Path $buildInfo['SrcRootPath'] 'Interop\LibLLVM') (Join-Path $buildInfo['SrcRootPath'] 'Interop\Ubiquity.NET.Llvm.Interop')
73105
if($LASTEXITCODE -eq 0)
74106
{

Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@
2222
<PackageReference Update="memberpage" Version="2.49.0" />
2323
<PackageReference Update="msdn.4.5.2" Version="0.1.0-alpha-1611021200" />
2424
<PackageReference Update="YamlDotNet" Version="8.1.0" />
25+
<PackageReference Update="google-diff-match-patch" Version="1.1.24" />
26+
<PackageReference Update="CommandLineParser" Version="2.8.0" />
2527
</ItemGroup>
2628
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="GlobalSuppressions.cs" company="Ubiquity.NET Contributors">
3+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
/* This file is used by Code Analysis to maintain SuppressMessage
8+
// attributes that are applied to this project.
9+
// Project-level suppressions either have no target or are given
10+
// a specific target and scoped to a namespace, type, member, etc.
11+
*/
12+
13+
using System.Diagnostics.CodeAnalysis;
14+
15+
[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Application doesn't provide public APIs" )]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="PatchOptions.cs" company="Ubiquity.NET Contributors">
3+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using System.Diagnostics.CodeAnalysis;
8+
9+
using CommandLine;
10+
11+
namespace PatchVsForLibClang
12+
{
13+
[Verb("patch", isDefault: true, HelpText = "patch the specified VS install")]
14+
public class PatchOptions
15+
{
16+
[SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "Not Hungarian notation" )]
17+
public PatchOptions(string vsInstallPath, bool force)
18+
{
19+
VsInstallPath = vsInstallPath;
20+
Force = force;
21+
}
22+
23+
[Value( 0, Required = true, HelpText = "Installation path to Visual Studio. [Normally retrieved from the 'InstallationPath' property of a VS Setup Instance class]" )]
24+
public string VsInstallPath { get; }
25+
26+
[Option('F', "Force", Required = false, HelpText = "Force patch, no prompting if patch applies to source [overwrites existing backup]")]
27+
public bool Force { get; }
28+
}
29+
30+
[Verb("diff", HelpText = "Generate diff file", Hidden = true)]
31+
[SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "All verb Options in one place" )]
32+
public class GenerateOptions
33+
{
34+
public GenerateOptions(string unpatched, string patched)
35+
{
36+
Unpatched = unpatched;
37+
Patched = patched;
38+
}
39+
40+
[Value( 0, Required = true, HelpText = "unpatched file for diff" )]
41+
public string Unpatched { get; }
42+
43+
[Value( 1, Required = true, HelpText = "patched file for diff" )]
44+
public string Patched { get; }
45+
}
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<Sdk Name="Microsoft.Build.CentralPackageVersions" />
4+
<PropertyGroup>
5+
<TargetFramework>netcoreapp3.1</TargetFramework>
6+
<OutputType>exe</OutputType>
7+
<OutputTypeEx>exe</OutputTypeEx>
8+
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
9+
<LangVersion>8.0</LangVersion>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<None Remove="intrin0_h.diff" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<EmbeddedResource Include="intrin0_h.diff" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="CommandLineParser" />
21+
<PackageReference Include="google-diff-match-patch" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30804.86
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PatchVsForLibClang", "PatchVsForLibClang.csproj", "{25A3C866-5F84-41A3-AA9C-1BAD214D1BD6}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{25A3C866-5F84-41A3-AA9C-1BAD214D1BD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{25A3C866-5F84-41A3-AA9C-1BAD214D1BD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{25A3C866-5F84-41A3-AA9C-1BAD214D1BD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{25A3C866-5F84-41A3-AA9C-1BAD214D1BD6}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {B73143CE-96C2-4FEA-9F76-3ACF66904B3A}
24+
EndGlobalSection
25+
EndGlobal

src/PatchVsForLibClang/Program.cs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="Program.cs" company="Ubiquity.NET Contributors">
3+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Text.RegularExpressions;
12+
13+
using CommandLine;
14+
15+
using DiffMatchPatch;
16+
17+
[assembly: CLSCompliant(true)]
18+
19+
namespace PatchVsForLibClang
20+
{
21+
internal class Program
22+
{
23+
// example paths
24+
// With Fix From MS => @"D:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\VC\Tools\MSVC\14.28.29617\include\intrin0.h";
25+
// MSVC 16.8.3 => @"D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\include\intrin0.h";
26+
27+
// VS 2019 16.9 Preview 2 is the first publicly available version with the fix included
28+
private static readonly Version MinVersionWithFix = new Version( 14, 28, 2933 );
29+
private const string MsVCRelativePath = @"VC\Tools\MSVC";
30+
private const string VersionRelativeRoot = "Include";
31+
32+
public static int Main( string[ ] args )
33+
{
34+
using var x = new Parser( );
35+
return x.ParseArguments( args, typeof(PatchOptions), typeof(GenerateOptions) )
36+
.MapResult( (PatchOptions o) => ApplyPatch( o ),
37+
(GenerateOptions o) => GenerateDiffFile(o),
38+
_ => -1 );
39+
}
40+
41+
private static int ApplyPatch(PatchOptions options)
42+
{
43+
if( !Directory.Exists( options.VsInstallPath ) )
44+
{
45+
Console.Error.WriteLine( "Specified VS install path does not exist. '{0}'", options.VsInstallPath );
46+
return -1;
47+
}
48+
49+
string msvcPath = Path.Combine( options.VsInstallPath, MsVCRelativePath );
50+
if( !Directory.Exists( msvcPath ) )
51+
{
52+
Console.Error.WriteLine( "Specified VS install path does not contain the MSVC libraries path. '{0}'", msvcPath );
53+
return -1;
54+
}
55+
56+
var versions = GetInstalledMsVcVersions( msvcPath ).ToList( );
57+
if( versions.Count == 0 )
58+
{
59+
Console.Error.WriteLine( "Could not find any installed versions at '{0}'", msvcPath );
60+
return -1;
61+
}
62+
63+
var maxInstalledVersion = versions.Max( );
64+
if( maxInstalledVersion <= MinVersionWithFix )
65+
{
66+
Console.WriteLine( "Installation already contains the fix from MS no files modified" );
67+
return 0;
68+
}
69+
70+
string versionRelativeIncludePath = Path.Combine( msvcPath, maxInstalledVersion.ToString( ), VersionRelativeRoot );
71+
if( !Directory.Exists( versionRelativeIncludePath ) )
72+
{
73+
Console.Error.WriteLine( "Could not find include path '{0}'", versionRelativeIncludePath );
74+
return -1;
75+
}
76+
77+
string intrin0_h_Path = Path.Combine( versionRelativeIncludePath, "intrin0.h" );
78+
if( !File.Exists( intrin0_h_Path ) )
79+
{
80+
Console.Error.WriteLine( "intrin0.h not found. ({0})", intrin0_h_Path );
81+
return -1;
82+
}
83+
84+
// compute patched content before trying backup as it might not apply to this install
85+
string patchedContent = PatchFile( intrin0_h_Path, Resources.Intrin0_h_diff );
86+
if( string.IsNullOrWhiteSpace( patchedContent ) )
87+
{
88+
Console.WriteLine( "File contents did not match - patches not applied." );
89+
return 0;
90+
}
91+
92+
string backupPath = Path.Combine( versionRelativeIncludePath, "intrin0.h.bak" );
93+
if( File.Exists( backupPath ) && !options.Force )
94+
{
95+
Console.WriteLine( "Backup already exists, no backup will be made" );
96+
}
97+
else
98+
{
99+
try
100+
{
101+
File.Copy( intrin0_h_Path, backupPath, false );
102+
}
103+
catch( IOException ex )
104+
{
105+
Console.Error.WriteLine( "ERROR creating backup: {0}", ex.Message );
106+
return -1;
107+
}
108+
}
109+
110+
File.WriteAllText( intrin0_h_Path, patchedContent );
111+
return 0;
112+
}
113+
114+
private static string PatchFile( string source, string diffText )
115+
{
116+
string fileText = File.ReadAllText( source );
117+
var patches = PatchList.Parse( diffText );
118+
var (patchedText, results) = patches.Apply( fileText );
119+
120+
// all patches must apply or the source wasn't a proper match
121+
return results.Aggregate( true, ( l, r ) => l & r ) ? patchedText : string.Empty;
122+
}
123+
124+
private static IEnumerable<Version> GetInstalledMsVcVersions( string msvcPath )
125+
{
126+
var verPattern = new Regex( @"\d+\.\d+\.\d+" );
127+
return from dir in Directory.EnumerateDirectories( msvcPath )
128+
where verPattern.IsMatch( dir )
129+
select Version.Parse( Path.GetFileName( dir ) );
130+
}
131+
132+
// While VS team did publish the GIT patch DIFF, that's not usable by the DiffMatchPatch library
133+
// (or any other I could find on short notice). So this is used to generate the DIFF applied.
134+
//
135+
// The resulting intrin0_h.diff is already part of this project so not likely needed, but
136+
// this is retained as a reference for how it was created)
137+
private static int GenerateDiffFile( GenerateOptions o )
138+
{
139+
string unpatched = File.ReadAllText( o.Unpatched );
140+
string patched = File.ReadAllText( o.Patched );
141+
142+
// compute and write the diff file for inclusion
143+
File.WriteAllText( "intrin0_h.diff", Patch.Compute( unpatched, patched ).ToText( ) );
144+
return 0;
145+
}
146+
}
147+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="Resources.cs" company="Ubiquity.NET Contributors">
3+
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using System;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.IO;
10+
11+
namespace PatchVsForLibClang
12+
{
13+
internal static class Resources
14+
{
15+
// to prevent problems with GIT line ending translation, convert line endings here to the LF form required by the DiffMatchPatch library...
16+
public static string Intrin0_h_diff => GetResourceStreamText( ResourceNames.Intrin0_h_diff ).Replace("\r\n","\n", StringComparison.Ordinal);
17+
18+
private static string GetResourceStreamText( string fileName )
19+
{
20+
string resName = "PatchVsForLibClang." + fileName;
21+
var manifestStream = typeof( Resources ).Assembly.GetManifestResourceStream( resName );
22+
if(manifestStream == null)
23+
{
24+
throw new FileNotFoundException( $"Resource {resName} not found!" );
25+
}
26+
27+
using var rdr = new StreamReader( manifestStream );
28+
return rdr.ReadToEnd( );
29+
}
30+
31+
private static class ResourceNames
32+
{
33+
[SuppressMessage( "Potential Code Quality Issues", "RECS0146:Member hides static member from outer class", Justification = "Intentional" )]
34+
[SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Intentional" )]
35+
internal const string Intrin0_h_diff = "intrin0_h.diff";
36+
}
37+
}
38+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@@ -428,251 +428,8 @@
2+
H_%0d%0a
3+
-#ifdef __clang__%0d%0a// This looks like a circular include but it is not because clang overrides %3cintrin.h%3e with their specific version.%0d%0a// See further discussion in LLVM-47099.%0d%0a#include %3cintrin.h%3e%0d%0a#else /* %5e%5e%5e __clang__ // !__clang__ vvv */%0d%0a
4+
#inc
5+
@@ -16617,24 +16617,43 @@
6+
r _Shift))%0d%0a
7+
+#ifndef __clang__%0d%0a
8+
__MACHINEX86
9+
@@ -16750,32 +16750,53 @@
10+
gned __int64))%0d%0a
11+
+#endif // __clang__%0d%0a
12+
__MACHINEX64(uns
13+
@@ -18275,31 +18275,4 @@
14+
*/%0d%0a
15+
-#endif /* %5e%5e%5e !__clang__ */

src/PatchVsForLibClang/readme.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# PatchVsForLibClang
2+
The VS headers contain a bug, tracked by [microsoft/STL bug 1300](https://github.com/microsoft/STL/issues/1300),
3+
that causes failures to parse correctly with LibClang, which is used by the LlvmBindingsGenerator.
4+
5+
Unfortunately the fix is not made as a hotpatch as of this time, and is only included in
6+
VS 2019 16.9 Preview 2. This makes it unavailable to automated builds where the hosting platform
7+
doesn't make environments available with preview releases of VS. Fortunately the fix is small,
8+
and the STL team even published the GIT patch file for intrin0.h (the core file that triggers
9+
the issues).
10+
11+
This project contains a program to apply a patch to the existing installation of VS (making a
12+
backup of the original, of course :grin:)
13+
14+
Usage:
15+
`PatchVsForLibClang <VsInstallPath>`
16+
17+
Where:
18+
VsInstallPath is the installation path of a VS instance. Normally this is retrieved in a PowerShell
19+
script from a VS Setup Instance (See the [vssetup.powershell](https://github.com/Microsoft/vssetup.powershell)
20+
repo for more information)
21+
22+
Example:
23+
`PatchVsForLibClang "D:\Program Files (x86)\Microsoft Visual Studio\2019\Community"`
24+
25+
This will patch the file @"D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\include\intrin0.h"
26+
creating a backup file (intrin0.h.bak) in the same directory

0 commit comments

Comments
 (0)