|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Diagnostics; |
| 4 | +using System.IO; |
| 5 | +using System.Reflection; |
| 6 | +using System.Runtime.CompilerServices; |
| 7 | +using System.Text; |
| 8 | +using System.Threading.Tasks; |
| 9 | +using Microsoft.Build.Locator; |
| 10 | +using Xunit.Abstractions; |
| 11 | + |
| 12 | +namespace Community.VisualStudio.Toolkit.UnitTests |
| 13 | +{ |
| 14 | + internal class TestProject |
| 15 | + { |
| 16 | + private static readonly VisualStudioInstance _instance = MSBuildLocator.RegisterDefaults(); |
| 17 | + |
| 18 | + private readonly string _directory; |
| 19 | + private readonly List<string> _files = new(); |
| 20 | + private readonly List<string> _propsImports = new(); |
| 21 | + private readonly List<string> _targetsImports = new(); |
| 22 | + private readonly List<string> _targetsElements = new(); |
| 23 | + |
| 24 | + public TestProject(string directory) |
| 25 | + { |
| 26 | + _directory = directory; |
| 27 | + } |
| 28 | + |
| 29 | + public void ImportTargets(string fileName) |
| 30 | + { |
| 31 | + string path = GetTargetsPath(fileName); |
| 32 | + _targetsImports.Add($"<Import Project='{path}' />"); |
| 33 | + } |
| 34 | + |
| 35 | + private static string GetTargetsPath(string fileName, [CallerFilePath] string thisFilePath = "") |
| 36 | + { |
| 37 | + return Path.GetFullPath( |
| 38 | + Path.Combine( |
| 39 | + Path.GetDirectoryName(thisFilePath), |
| 40 | + $"../../../../src/toolkit/nuget/build/{fileName}" |
| 41 | + ) |
| 42 | + ); |
| 43 | + } |
| 44 | + |
| 45 | + public void AddFile(string fileName, string contents) |
| 46 | + { |
| 47 | + string fullPath = Path.Combine(_directory, fileName); |
| 48 | + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); |
| 49 | + File.WriteAllText(fullPath, contents); |
| 50 | + |
| 51 | + _files.Add($"<Compile Include='{fileName}'/>"); |
| 52 | + } |
| 53 | + |
| 54 | + public void AddTargetElement(string element) |
| 55 | + { |
| 56 | + _targetsElements.Add(element); |
| 57 | + } |
| 58 | + |
| 59 | + public async Task<CompilationResult> CompileAsync(CompileOptions options, ITestOutputHelper outputHelper) |
| 60 | + { |
| 61 | + WriteFiles(); |
| 62 | + |
| 63 | + using (Process process = new()) |
| 64 | + { |
| 65 | + List<string> stdout = new(); |
| 66 | + List<string> stderr = new(); |
| 67 | + List<string> arguments = new(); |
| 68 | + |
| 69 | + arguments.Add("/Restore"); |
| 70 | + arguments.Add($"/t:{options.Target}"); |
| 71 | + arguments.Add("/nr:false"); // Disable node re-use. |
| 72 | + |
| 73 | + foreach (PropertyInfo property in options.Properties.GetType().GetProperties()) |
| 74 | + { |
| 75 | + object value = property.GetValue(options.Properties); |
| 76 | + if (value is not null) |
| 77 | + { |
| 78 | + arguments.Add($"/p:{property.Name}={value}"); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + foreach (string argument in options.Arguments) |
| 83 | + { |
| 84 | + arguments.Add(argument); |
| 85 | + } |
| 86 | + |
| 87 | + process.StartInfo = new ProcessStartInfo |
| 88 | + { |
| 89 | + FileName = Path.Combine(_instance.MSBuildPath, "MSBuild.exe"), |
| 90 | + Arguments = string.Join(" ", arguments), |
| 91 | + WorkingDirectory = _directory, |
| 92 | + UseShellExecute = false, |
| 93 | + RedirectStandardOutput = true, |
| 94 | + RedirectStandardError = true, |
| 95 | + StandardOutputEncoding = Encoding.UTF8, |
| 96 | + StandardErrorEncoding = Encoding.UTF8 |
| 97 | + }; |
| 98 | + |
| 99 | + foreach (KeyValuePair<string, string> entry in options.Environment) |
| 100 | + { |
| 101 | + process.StartInfo.Environment[entry.Key] = entry.Value; |
| 102 | + } |
| 103 | + |
| 104 | + process.Start(); |
| 105 | + |
| 106 | + await Task.WhenAll( |
| 107 | + DrainReaderAsync(process.StandardOutput, (line) => |
| 108 | + { |
| 109 | + stdout.Add(line); |
| 110 | + outputHelper.WriteLine(line); |
| 111 | + }), |
| 112 | + DrainReaderAsync(process.StandardError, (line) => |
| 113 | + { |
| 114 | + stderr.Add(line); |
| 115 | + outputHelper.WriteLine(line); |
| 116 | + }) |
| 117 | + ); |
| 118 | + |
| 119 | + process.WaitForExit(); |
| 120 | + |
| 121 | + return new CompilationResult(process.ExitCode, stdout, stderr); |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + private void WriteFiles() |
| 126 | + { |
| 127 | + WriteManifest(); |
| 128 | + WriteProject(); |
| 129 | + } |
| 130 | + |
| 131 | + private void WriteProject() |
| 132 | + { |
| 133 | + string projectName = $"{Path.GetFileName(_directory)}.csproj"; |
| 134 | + string projectFileName = Path.Combine(_directory, projectName); |
| 135 | + |
| 136 | + File.WriteAllText( |
| 137 | + projectFileName, |
| 138 | + $@" |
| 139 | +<Project ToolsVersion='17.0' DefaultTargets='Build' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'> |
| 140 | + <PropertyGroup> |
| 141 | + <VSToolsPath Condition=""'$(VSToolsPath)' == ''"">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> |
| 142 | + </PropertyGroup> |
| 143 | + <Import Project='$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props' Condition=""Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"" /> |
| 144 | + {string.Join(Environment.NewLine, _propsImports)} |
| 145 | + <PropertyGroup> |
| 146 | + <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration> |
| 147 | + <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform> |
| 148 | + <ProjectTypeGuids>{{82b43b9b-a64c-4715-b499-d71e9ca2bd60}};{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}</ProjectTypeGuids> |
| 149 | + <ProjectGuid>{{C313D707-2A74-4AD2-BB5D-0FD6C4942E08}}</ProjectGuid> |
| 150 | + <OutputType>Library</OutputType> |
| 151 | + <RootNamespace>Test.Extension</RootNamespace> |
| 152 | + <AssemblyName>Extension</AssemblyName> |
| 153 | + <TargetFrameworkVersion>v4.8</TargetFrameworkVersion> |
| 154 | + <OutputPath>bin\$(Configuration)</OutputPath> |
| 155 | + <GeneratePkgDefFile>false</GeneratePkgDefFile> |
| 156 | + <DeployExtension>False</DeployExtension> |
| 157 | + </PropertyGroup> |
| 158 | + <ItemGroup> |
| 159 | + <None Include='source.extension.vsixmanifest'/> |
| 160 | + </ItemGroup> |
| 161 | + <ItemGroup> |
| 162 | + {string.Join(Environment.NewLine, _files)} |
| 163 | + </ItemGroup> |
| 164 | + <ItemGroup> |
| 165 | + <PackageReference Include='Microsoft.VSSDK.BuildTools' Version='17.0.5232'> |
| 166 | + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| 167 | + <PrivateAssets>all</PrivateAssets> |
| 168 | + </PackageReference> |
| 169 | + </ItemGroup> |
| 170 | + {string.Join(Environment.NewLine, _targetsElements)} |
| 171 | + {string.Join(Environment.NewLine, _targetsImports)} |
| 172 | + <Import Project='$(MSBuildToolsPath)\Microsoft.CSharp.targets' /> |
| 173 | + <Import Project='$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets' Condition=""'$(VSToolsPath)' != ''"" /> |
| 174 | +</Project>"); |
| 175 | + } |
| 176 | + |
| 177 | + private void WriteManifest() |
| 178 | + { |
| 179 | + File.WriteAllText( |
| 180 | + Path.Combine(_directory, "source.extension.vsixmanifest"), |
| 181 | + @"<?xml version='1.0' encoding='utf-8'?> |
| 182 | +<PackageManifest Version='2.0.0' xmlns='http://schemas.microsoft.com/developer/vsx-schema/2011' xmlns:d='http://schemas.microsoft.com/developer/vsx-schema-design/2011'> |
| 183 | + <Metadata> |
| 184 | + <Identity Id='VSSDK.TestExtension.5a9a059d-5738-41dc-9075-250890b4ef6f' Version='1.0' Language='en-US' Publisher='Mads Kristensen' /> |
| 185 | + <DisplayName>VSSDK.TestExtension</DisplayName> |
| 186 | + <Description>Empty VSIX Project.</Description> |
| 187 | + </Metadata> |
| 188 | + <Installation> |
| 189 | + <InstallationTarget Id='Microsoft.VisualStudio.Community' Version='[16.0, 17.0)' /> |
| 190 | + </Installation> |
| 191 | + <Dependencies> |
| 192 | + <Dependency Id='Microsoft.Framework.NDP' DisplayName='Microsoft .NET Framework' d:Source='Manual' Version='[4.5,)' /> |
| 193 | + </Dependencies> |
| 194 | + <Prerequisites> |
| 195 | + <Prerequisite Id='Microsoft.VisualStudio.Component.CoreEditor' Version='[16.0,17.0)' DisplayName='Visual Studio core editor' /> |
| 196 | + </Prerequisites> |
| 197 | + <Assets> |
| 198 | + <Asset Type='Microsoft.VisualStudio.VsPackage' d:Source='Project' d:ProjectName='%CurrentProject%' Path='|%CurrentProject%;PkgdefProjectOutputGroup|' /> |
| 199 | + <Asset Type='Microsoft.VisualStudio.MefComponent' d:Source='Project' d:ProjectName='%CurrentProject%' Path='|%CurrentProject%|' /> |
| 200 | + </Assets> |
| 201 | +</PackageManifest>"); |
| 202 | + } |
| 203 | + |
| 204 | + private static async Task DrainReaderAsync(StreamReader reader, Action<string> output) |
| 205 | + { |
| 206 | + string line; |
| 207 | + while ((line = await reader.ReadLineAsync()) is not null) |
| 208 | + { |
| 209 | + output(line); |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | +} |
0 commit comments