Use assembly load context for extensions (#558)

If an extension contains multiple assemblies, they will now resolve from within the extension thanks to a private load context. We want to ensure that we don't load a different version of anything the default context loads, so this checks for that as well.
This commit is contained in:
Taylor Southwick 2021-05-29 10:27:42 -07:00 коммит произвёл GitHub
Родитель 550aa94351
Коммит 6a77f069d4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
33 изменённых файлов: 353 добавлений и 146 удалений

Просмотреть файл

@ -13,19 +13,15 @@
<Compile Include="$(MSBuildThisFileDirectory)/shared/**/*.cs" />
</ItemGroup>
<PropertyGroup>
<ExcludeMSBuildRuntime Condition=" '$(ExcludeMSBuildRuntime)' =='' ">false</ExcludeMSBuildRuntime>
</PropertyGroup>
<!-- This forces the MSBuild runtime assets to be excluded from build. We have this opt-in because
otherwise it will add these as dependencies to all projects. -->
<ItemGroup Condition="$(ExcludeMSBuildRuntime)">
<ItemGroup>
<!-- Exclude MSBuild runtime assets from both src and test projects
as they shouldn't be present in this solution's output paths.
Instead, these dependencies should be loaded from the selected
MSBuild's location. -->
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Framework" ExcludeAssets="runtime" />
<PackageReference Update="Microsoft.Build" ExcludeAssets="runtime" />
<PackageReference Update="Microsoft.Build.Framework" ExcludeAssets="runtime" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)/Extensions.targets" />
</Project>

96
Extensions.targets Normal file
Просмотреть файл

@ -0,0 +1,96 @@
<Project>
<!-- Publish the extension and collect its output -->
<Target Name="ComputePublishOutput" DependsOnTargets="Build;ComputeFilesToPublish" Returns="@(ExtensionFiles)">
<ItemGroup>
<ExtensionFiles Include="@(ResolvedFileToPublish)">
<Link>$(ExtensionDir)/%(ResolvedFileToPublish.RelativePath)</Link>
<TargetPath>$(ExtensionDir)/%(ResolvedFileToPublish.RelativePath)</TargetPath>
</ExtensionFiles>
</ItemGroup>
</Target>
<!-- If a project is an extension, its reference to the abstractions should not be copied -->
<Target Name="MarkExtensionPrivateAssemblies" Condition=" '$(_IsExtension)' == 'true' " BeforeTargets="PrepareForBuild">
<ItemGroup>
<ProjectReference Update="@(ProjectReference)" Condition=" '%(ProjectReference.FileName)' == 'Microsoft.DotNet.UpgradeAssistant.Abstractions' ">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
</Target>
<!--
Since we will be publishing extensions, we want to ensure they are restored. Must run before the following targets:
Restore: For clean builds
PrepareForBuild: For incremental builds in VS
PackDependsOn: For pack commands
-->
<Target Name="RestoreExtensions" BeforeTargets="Restore;PrepareForBuild;$(PackDependsOn)">
<MSBuild Projects="%(Extension.Identity)"
Targets="Restore"
Properties="Configuration=$(Configuration)"
RemoveProperties="TargetFramework"
Condition=" '%(Extension.Name)' != '' " />
</Target>
<!-- Publish each extension into its own directory -->
<Target Name="PublishUpgradeAssistantExtensions" DependsOnTargets="ResolveAssemblyReferences" BeforeTargets="AssignTargetPaths" Outputs="%(Extension.Identity)">
<!-- Add the relative directory the extension will be added to -->
<ItemGroup>
<Extension Update="@(Extension)">
<ExtensionDir>extensions/%(Extension.Name)</ExtensionDir>
<!-- We want to set a new intermediate path to prevent race conditions of multiple writes to the output -->
<IntermediateOutputPath>$(BaseIntermediateOutputPath)\$(Configuration)\extensions\%(Extension.Name)\</IntermediateOutputPath>
</Extension>
</ItemGroup>
<Message Text="Publishing extension %(Extension.Name)" Importance="high" Condition=" '%(Extension.Name)' != '' "/>
<!--
Publish the extension and collect its extension.
- We also pass in some custom configuration so it'll know its an extension.
- We want to remove any TargetFramework that is set so that isn't flowed through to the next project.
-->
<MSBuild Projects="%(Extension.Identity)"
Targets="ComputePublishOutput"
RemoveProperties="TargetFramework"
Properties="Configuration=$(Configuration);IntermediateOutputPath=%(Extension.IntermediateOutputPath);ExtensionDir=%(Extension.ExtensionDir);_IsExtension=true"
Condition=" '%(Extension.Name)' != '' ">
<Output TaskParameter="TargetOutputs" ItemName="_ExtensionArtifacts" />
</MSBuild>
<ItemGroup>
<!-- Create a list of all assemblies included by host -->
<_ExcludeFromExtension Include="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
<_ExcludeFromExtension Include="%(RuntimeCopyLocalItems.DestinationSubPath)" />
<!-- Create a collection of the extension files by their relative path while maintaining metadata -->
<_ExtensionArtifactsByRelativePath Include="%(_ExtensionArtifacts.RelativePath)">
<OriginalIdentity>%(Identity)</OriginalIdentity>
<TargetPath>%(TargetPath)</TargetPath>
<Link>%(Link)</Link>
</_ExtensionArtifactsByRelativePath>
<!-- Remove the host supplied assemblies -->
<_FilteredExtensionArtifactsByRelativePath Include="@(_ExtensionArtifactsByRelativePath)" Exclude="@(_ExcludeFromExtension)" />
<!-- Transform the filtered list back to include the appropriate metadata to be added to -->
<_FilteredExtensionArtifacts Include="%(_FilteredExtensionArtifactsByRelativePath.OriginalIdentity)">
<RelativePath>%(TargetPath)</RelativePath>
<TargetPath>%(TargetPath)</TargetPath>
<Link>%(Link)</Link>
</_FilteredExtensionArtifacts>
<None Include="@(_FilteredExtensionArtifacts)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Message Text="Published extension %(Extension.Name)" Importance="high" Condition=" '%(Extension.Name)' != '' "/>
</Target>
</Project>

Просмотреть файл

@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
Extensions.targets = Extensions.targets
GitVersion.yml = GitVersion.yml
global.json = global.json
LICENSE.txt = LICENSE.txt

Просмотреть файл

@ -4,25 +4,24 @@
</PropertyGroup>
<Target Name="RestoreAndCopyTryConvert" BeforeTargets="AssignTargetPaths" Condition="$(InstallTryConvert)">
<PropertyGroup>
<!-- By default, NuGet will not include files starting with '.' or ending with '.nupkg'. Since we are including try-convert via dotnet tools, some of the files fit into this. -->
<NoDefaultExcludes>true</NoDefaultExcludes>
<ToolsDirectory>$(MSBuildThisFileDirectory).tools\</ToolsDirectory>
<TryConvertVersion>0.7.226301</TryConvertVersion>
<TryConvertDirectory>$(ToolsDirectory)try-convert\$(TryConvertVersion)\</TryConvertDirectory>
<TryConvertDirectory>$(ToolsDirectory)try-convert\$(TryConvertVersion)</TryConvertDirectory>
<TryConvertSubDirectory>$(ToolsDirectory)try-convert\$(TryConvertVersion)\.store\try-convert\$(TryConvertVersion)\try-convert\$(TryConvertVersion)\tools\net5.0\any</TryConvertSubDirectory>
</PropertyGroup>
<Exec Command="dotnet tool install try-convert --version $(TryConvertVersion) --tool-path $(TryConvertDirectory)" Condition="!Exists($(TryConvertDirectory))" />
<ItemGroup>
<__TryConvertContents Include="$(TryConvertDirectory)\**\*" />
<__TryConvertContents Include="$(TryConvertSubDirectory)\**\*" />
<None Include="@(__TryConvertContents)">
<Link>tools/%(RecursiveDir)%(Filename)%(Extension)</Link>
<Link>try-convert/%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Target>
</Project>

Просмотреть файл

@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
@ -10,14 +11,14 @@
<PackageId>upgrade-assistant</PackageId>
<PackageReleaseNotes>A changelog is available at https://github.com/dotnet/upgrade-assistant/blob/main/CHANGELOG.md.</PackageReleaseNotes>
<PackageIcon>icon.png</PackageIcon>
<InstallTryConvert>true</InstallTryConvert>
<ExcludeMSBuildRuntime>true</ExcludeMSBuildRuntime>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" />
@ -28,6 +29,7 @@
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
<ItemGroup>
<!-- Explicitly reference packages that we do *not* want included in output
paths in case transitive dependencies pull them in. These runtime
@ -49,13 +51,18 @@
<PackageReference Include="NuGet.Protocol" ExcludeAssets="runtime" />
<PackageReference Include="NuGet.Versioning" ExcludeAssets="runtime" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Microsoft.DotNet.UpgradeAssistant.Abstractions\Microsoft.DotNet.UpgradeAssistant.Abstractions.csproj" />
<ProjectReference Include="..\..\steps\Microsoft.DotNet.UpgradeAssistant.Steps.Backup\Microsoft.DotNet.UpgradeAssistant.Steps.Backup.csproj" />
<ProjectReference Include="..\..\components\Microsoft.DotNet.UpgradeAssistant\Microsoft.DotNet.UpgradeAssistant.csproj" />
<ProjectReference Include="..\..\components\Microsoft.DotNet.UpgradeAssistant.MSBuild\Microsoft.DotNet.UpgradeAssistant.MSBuild.csproj" />
<ProjectReference Include="..\..\components\Microsoft.DotNet.UpgradeAssistant.Extensions\Microsoft.DotNet.UpgradeAssistant.Extensions.csproj" />
<!-- This isn't used directly, but needs to be referenced so that the binary is available at runtime to be registered as an extension -->
<ProjectReference Include="..\..\extensions\default\Microsoft.DotNet.UpgradeAssistant.Extensions.Default\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.csproj" />
</ItemGroup>
<ItemGroup>
<Extension Include="..\..\extensions\default\Microsoft.DotNet.UpgradeAssistant.Extensions.Default\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.csproj" >
<Name>Default</Name>
</Extension>
</ItemGroup>
</Project>

Просмотреть файл

@ -1,38 +1,13 @@
{
"ConfigUpdater": {
"ConfigFilePaths": [
"app.config",
"web.config",
"Views\\web.config"
]
},
"PackageUpdater": {
"PackageMapPath": "PackageMaps"
},
"TemplateInserter": {
"TemplateConfigFiles": [
"Templates\\CSharpWebAppTemplates\\WebAppTemplates.json",
"Templates\\VisualBasicWebAppTemplates\\WebAppTemplates.json"
]
},
"SourceUpdater": {
"AdditionalAnalyzerTexts": [
"WebTypeReplacements.typemap"
]
},
"DefaultTargetFrameworks": {
"Current": "net5.0",
"LTS": "netcoreapp3.1",
"Preview": "net6.0"
},
"TryConvertProjectConverter": {
"TryConvertPath": "./tools/try-convert.exe"
},
"UpgradeAssistantExtensionPaths": "",
"Portability": {
"ServiceEndpoint": "https://portability.dot.net"
},
"ExtensionServiceProviders": [
"Microsoft.DotNet.UpgradeAssistant.Extensions.Default.dll"
"DefaultExtensions": [
"Default"
]
}

Просмотреть файл

@ -3,6 +3,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
@ -19,6 +20,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
/// </summary>
IServiceCollection Services { get; }
/// <summary>
/// Gets the file provider for the root of the extension.
/// </summary>
IFileProvider Files { get; }
/// <summary>
/// Add options that are supplied within an extension manifest.
///

Просмотреть файл

@ -17,6 +17,7 @@
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>Upgrade Assistant</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
@ -28,6 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
{
internal class ExtensionAssemblyLoadContext : AssemblyLoadContext
{
private const string ALC_Prefix = "UA_";
private readonly ExtensionInstance _extension;
public ExtensionAssemblyLoadContext(ExtensionInstance extension)
: base(ALC_Prefix + extension.Name)
{
_extension = extension;
}
protected override Assembly? Load(AssemblyName assemblyName)
{
// If available in the default, we want to ensure that is used.
var inDefault = Default.Assemblies.FirstOrDefault(a => string.Equals(a.GetName().Name, assemblyName.Name, StringComparison.Ordinal));
if (inDefault is Assembly existing)
{
return existing;
}
var dll = $"{assemblyName.Name}.dll";
var dllFile = _extension.FileProvider.GetFileInfo(dll);
if (dllFile.Exists)
{
using var dllStream = dllFile.CreateReadStream();
var pdb = $"{assemblyName.Name}.pdb";
var pdbFile = _extension.FileProvider.GetFileInfo(pdb);
if (pdbFile.Exists)
{
using var pdbStream = pdbFile.CreateReadStream();
return LoadFromStream(dllStream, pdbStream);
}
else
{
return LoadFromStream(dllStream);
}
}
return null;
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Runtime.Loader;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
@ -14,15 +15,26 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
private const string ExtensionNamePropertyName = "ExtensionName";
private const string DefaultExtensionName = "Unknown";
private readonly Lazy<AssemblyLoadContext> _alc;
public ExtensionInstance(IFileProvider fileProvider, string? name = null, IConfiguration? configuration = null)
{
FileProvider = fileProvider;
Configuration = configuration ?? CreateConfiguration(fileProvider);
Name = name ?? GetName(Configuration, FileProvider);
_alc = new Lazy<AssemblyLoadContext>(() => new ExtensionAssemblyLoadContext(this));
}
public string Name { get; }
public bool HasAssemblyLoadContext => _alc.IsValueCreated;
/// <summary>
/// Gets the <see cref="AssemblyLoadContext"/> for the extension. Guard calls with <see cref="HasAssemblyLoadContext"/> first,
/// otherwise it may trigger creation of the load context if it is not needed.
/// </summary>
public AssemblyLoadContext LoadContext => _alc.Value;
public IFileProvider FileProvider { get; }
public IConfiguration Configuration { get; }

Просмотреть файл

@ -40,7 +40,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
foreach (var match in other.Files.GetFiles(path))
{
var fileInfo = other.Files.GetFileInfo(match.Path);
var directory = Path.GetDirectoryName(match.Path);
var directory = Path.GetDirectoryName(match.Path)!;
var newFileProvider = new SubFileProvider(other.Files, directory);
foreach (var obj in ReadAll(fileInfo))

Просмотреть файл

@ -5,12 +5,11 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
{
@ -60,9 +59,6 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Will be disposed by dependency injection.")]
private static IEnumerable<ExtensionInstance> GetExtensions(IConfiguration originalConfiguration, IEnumerable<string> additionalExtensionPaths)
{
// Always include the default extension which contains built-in source updaters, config updaters, etc.
yield return new ExtensionInstance(new PhysicalFileProvider(AppContext.BaseDirectory), "Default extension", originalConfiguration);
foreach (var e in GetExtensionPaths())
{
if (string.IsNullOrEmpty(e))
@ -78,10 +74,16 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
IEnumerable<string> GetExtensionPaths()
{
const string ExtensionDirectory = "extensions";
var fromConfig = originalConfiguration.GetSection("DefaultExtensions")
.Get<string[]>()
.Select(n => Path.GetFullPath(Path.Combine(ExtensionDirectory, n)));
var extensionPathString = originalConfiguration[UpgradeAssistantExtensionPathsSettingName];
var pathsFromString = extensionPathString?.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty<string>();
return pathsFromString.Concat(additionalExtensionPaths);
return fromConfig.Concat(pathsFromString).Concat(additionalExtensionPaths);
}
}
@ -116,21 +118,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
continue;
}
// AssemblyLoadContext is not available in .NET Standard 2.0
// var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream);
var assemblyBytes = new byte[assemblyStream.Length];
assemblyStream.Read(assemblyBytes, 0, assemblyBytes.Length);
var assembly = Assembly.Load(assemblyBytes);
var serviceProviders = assembly.GetTypes()
.Where(t => t.IsPublic && !t.IsAbstract && typeof(IExtensionServiceProvider).IsAssignableFrom(t))
.Select(t => Activator.CreateInstance(t))
.Cast<IExtensionServiceProvider>();
foreach (var sp in serviceProviders)
{
sp.AddServices(new ExtensionServiceCollection(services, extension.Configuration));
}
extension.LoadContext.LoadFromStream(assemblyStream);
}
catch (FileLoadException)
{
@ -139,6 +127,22 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
{
}
}
if (!extension.HasAssemblyLoadContext)
{
return;
}
var serviceProviders = extension.LoadContext.Assemblies.SelectMany(assembly => assembly
.GetTypes()
.Where(t => t.IsPublic && !t.IsAbstract && typeof(IExtensionServiceProvider).IsAssignableFrom(t))
.Select(t => Activator.CreateInstance(t))
.Cast<IExtensionServiceProvider>());
foreach (var sp in serviceProviders)
{
sp.AddServices(new ExtensionServiceCollection(services, extension));
}
}
}
}

Просмотреть файл

@ -4,12 +4,17 @@
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
{
public record ExtensionServiceCollection(IServiceCollection Services, IConfiguration Configuration) : IExtensionServiceCollection
internal record ExtensionServiceCollection(IServiceCollection Services, ExtensionInstance Extension) : IExtensionServiceCollection
{
public IConfiguration Configuration => Extension.Configuration;
public IFileProvider Files => Extension.FileProvider;
public IExtensionOptionsBuilder<TOption> AddExtensionOption<TOption>(string sectionName)
where TOption : class, new()
{

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

Просмотреть файл

@ -25,19 +25,19 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions.Default
throw new ArgumentNullException(nameof(services));
}
AddUpgradeSteps(services, services.Configuration);
AddUpgradeSteps(services);
AddConfigUpdaters(services.Services);
AddAnalyzersAndCodeFixProviders(services.Services);
AddPackageReferenceAnalyzers(services.Services);
}
private static void AddUpgradeSteps(IExtensionServiceCollection services, IConfiguration configuration)
private static void AddUpgradeSteps(IExtensionServiceCollection services)
{
services.Services.AddBackupStep();
services.AddConfigUpdaterStep();
services.AddPackageUpdaterStep();
services.Services.AddProjectFormatSteps()
.Bind(configuration.GetSection(TryConvertProjectConverterStepOptionsSection));
services.AddProjectFormatSteps()
.Bind(services.Configuration.GetSection(TryConvertProjectConverterStepOptionsSection));
services.Services.AddSolutionSteps();
services.AddSourceUpdaterStep();
services.AddTemplateInserterStep();

Просмотреть файл

@ -0,0 +1,36 @@
{
"ExtensionName": "Default",
"ConfigUpdater": {
"ConfigFilePaths": [
"app.config",
"web.config",
"Views\\web.config"
]
},
"TryConvertProjectConverter": {
"TryConvertPath": "./try-convert/try-convert.dll"
},
"SourceUpdater": {
"AdditionalAnalyzerTexts": [
"WebTypeReplacements.typemap"
]
},
"PackageUpdater": {
"PackageMapPath": "PackageMaps"
},
"TemplateInserter": {
"TemplateConfigFiles": [
"Templates\\CSharpWebAppTemplates\\WebAppTemplates.json",
"Templates\\VisualBasicWebAppTemplates\\WebAppTemplates.json"
]
},
"ExtensionServiceProviders": [
"Microsoft.DotNet.UpgradeAssistant.Extensions.Default.dll"
]
}

Просмотреть файл

@ -20,4 +20,10 @@
<ProjectReference Include="..\..\..\steps\Microsoft.DotNet.UpgradeAssistant.Steps.Razor\Microsoft.DotNet.UpgradeAssistant.Steps.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="ExtensionManifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -4,7 +4,6 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<IsPackable>true</IsPackable>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<PackageId>Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers</PackageId>
@ -24,6 +23,7 @@
<ItemGroup>
<ProjectReference Include="..\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.CodeFixes\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.CodeFixes.csproj" />
<ProjectReference Include="..\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers.csproj" />
<ProjectReference Include="..\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers.Common\Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers.Common.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<None Include="TypeMaps.props" CopyToOutputDirectory="Always" />
<None Include="WebTypeReplacements.typemap" CopyToOutputDirectory="Always" />
<None Include="WebTypeReplacements.typemap" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" />

Просмотреть файл

@ -10,9 +10,4 @@
<ItemGroup>
<ProjectReference Include="..\..\common\Microsoft.DotNet.UpgradeAssistant.Abstractions\Microsoft.DotNet.UpgradeAssistant.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.FileSystem.Primitives" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="System.Text.Encoding.Extensions" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,15 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<InstallTryConvert>true</InstallTryConvert>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Microsoft.DotNet.UpgradeAssistant.Abstractions\Microsoft.DotNet.UpgradeAssistant.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
</ItemGroup>
</Project>

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.IO;
using Microsoft.DotNet.UpgradeAssistant.Extensions;
using Microsoft.DotNet.UpgradeAssistant.Steps.ProjectFormat;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -11,21 +12,30 @@ namespace Microsoft.DotNet.UpgradeAssistant
{
public static class ProjectFormatStepsExtensions
{
public static OptionsBuilder<TryConvertProjectConverterStepOptions> AddProjectFormatSteps(this IServiceCollection services)
public static OptionsBuilder<TryConvertProjectConverterStepOptions> AddProjectFormatSteps(this IExtensionServiceCollection services)
{
services.AddUpgradeStep<SetTFMStep>();
services.AddUpgradeStep<TryConvertProjectConverterStep>();
services.AddSingleton<ITryConvertTool, TryConvertTool>();
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
return services.AddOptions<TryConvertProjectConverterStepOptions>()
services.Services.AddUpgradeStep<SetTFMStep>();
services.Services.AddUpgradeStep<TryConvertProjectConverterStep>();
services.Services.AddSingleton<ITryConvertTool, TryConvertTool>();
return services.Services.AddOptions<TryConvertProjectConverterStepOptions>()
.PostConfigure(options =>
{
var path = Environment.ExpandEnvironmentVariables(options.TryConvertPath);
if (!Path.IsPathRooted(path))
{
var directory = Path.GetDirectoryName(typeof(ProjectFormatStepsExtensions).Assembly.Location);
path = Path.GetFullPath(Path.Combine(directory, options.TryConvertPath));
var fileInfo = services.Files.GetFileInfo(options.TryConvertPath);
if (fileInfo.Exists && fileInfo.PhysicalPath is string physicalPath)
{
path = physicalPath;
}
}
options.TryConvertPath = path;

Просмотреть файл

@ -17,8 +17,9 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.ProjectFormat
{
public class TryConvertTool : ITryConvertTool
{
private const string StorePath = ".store/try-convert";
private const string TryConvertArgumentsFormat = "--no-backup -m \"{0}\" --force-web-conversion --keep-current-tfms -p \"{1}\"";
private const string DotNetCli = "dotnet";
private const string TryConvertArgumentsFormat = "{0} --no-backup -m \"{1}\" --force-web-conversion --keep-current-tfms -p \"{2}\"";
private static readonly string[] ErrorMessages = new[]
{
"This project has custom imports that are not accepted by try-convert",
@ -72,7 +73,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.ProjectFormat
return _runner.RunProcessAsync(new ProcessInfo
{
Command = Path,
Command = DotNetCli,
Arguments = GetArguments(project.Required()),
EnvironmentVariables = context.GlobalProperties,
IsErrorFilter = data => ErrorMessages.Any(data.Contains),
@ -93,23 +94,9 @@ namespace Microsoft.DotNet.UpgradeAssistant.Steps.ProjectFormat
: productVersion;
}
// Local .NET CLI tools (like try-convert) typically have their implementations in a version-specific
// folder inside the hidden .store path next to the host. In case the version being stored in the
// tool's product version attribute ever changes, this could be used as a backup means of getting
// try-convert's version.
var storeDir = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Path), StorePath);
if (Directory.Exists(storeDir))
{
var versionDirs = Directory.GetDirectories(storeDir);
if (versionDirs.Length == 1)
{
return System.IO.Path.GetFileName(versionDirs[0]);
}
}
return null;
}
private string GetArguments(IProject project) => string.Format(CultureInfo.InvariantCulture, TryConvertArgumentsFormat, GetMSBuildPath(), project.Required().FileInfo);
private string GetArguments(IProject project) => string.Format(CultureInfo.InvariantCulture, TryConvertArgumentsFormat, Path, GetMSBuildPath(), project.Required().FileInfo);
}
}

Просмотреть файл

@ -12,7 +12,6 @@
<PackageReference Include="DiffPlex" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" />
<PackageReference Include="Microsoft.Bcl.Hashcode" />
<PackageReference Include="Microsoft.CodeAnalysis.Razor" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>

Просмотреть файл

@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.DotNet.UpgradeAssistant.Steps.Source
{
internal class FileInfoAdditionalText : AdditionalText
{
private readonly IFileInfo _file;
public FileInfoAdditionalText(IFileInfo file)
{
_file = file;
}
public override string Path => _file.PhysicalPath ?? _file.Name;
public override SourceText? GetText(CancellationToken cancellationToken = default)
{
using var stream = _file.CreateReadStream();
return SourceText.From(stream);
}
}
}

Просмотреть файл

@ -9,6 +9,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" />
</ItemGroup>
</Project>

Просмотреть файл

@ -2,11 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.DotNet.UpgradeAssistant.Steps.Source
{
public class SourceUpdaterOptions
public class SourceUpdaterOptions : IFileOption
{
public string[] AdditionalAnalyzerTexts { get; set; } = Array.Empty<string>();
public IFileProvider Files { get; set; } = null!;
}
}

Просмотреть файл

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.DotNet.UpgradeAssistant.Extensions;
using Microsoft.DotNet.UpgradeAssistant.Steps.Source;
@ -31,10 +30,22 @@ namespace Microsoft.DotNet.UpgradeAssistant
// with json serialized files.
services.Services.AddTransient<IEnumerable<AdditionalText>>(sp =>
{
var options = sp.GetRequiredService<IOptions<ICollection<SourceUpdaterOptions>>>().Value;
var textPaths = options.SelectMany(o => o.AdditionalAnalyzerTexts);
return textPaths.Select(p => new AdditionalFileText(p));
var options = sp.GetRequiredService<IOptions<ICollection<SourceUpdaterOptions>>>();
return ExpandAdditionalTexts(options.Value);
});
}
private static IEnumerable<AdditionalText> ExpandAdditionalTexts(IEnumerable<SourceUpdaterOptions> options)
{
foreach (var option in options)
{
foreach (var text in option.AdditionalAnalyzerTexts)
{
var fileInfo = option.Files.GetFileInfo(text);
yield return new FileInfoAdditionalText(fileInfo);
}
}
}
}
}

Просмотреть файл

@ -13,8 +13,4 @@
<ItemGroup>
<ProjectReference Include="..\..\common\Microsoft.DotNet.UpgradeAssistant.Abstractions\Microsoft.DotNet.UpgradeAssistant.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>

Просмотреть файл

@ -3,7 +3,6 @@
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<ExcludeMSBuildRuntime>true</ExcludeMSBuildRuntime>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" />

Просмотреть файл

@ -3,7 +3,6 @@
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<ExcludeMSBuildRuntime>true</ExcludeMSBuildRuntime>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" />

Просмотреть файл

@ -4,7 +4,6 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<IsTestProject>true</IsTestProject>
<ExcludeMSBuildRuntime>true</ExcludeMSBuildRuntime>
<!-- Ignore the .ConfigureAwait(false) warning -->
<NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup>

Просмотреть файл

@ -3,8 +3,6 @@
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsUnitTestProject>true</IsUnitTestProject>
<InstallTryConvert>true</InstallTryConvert>
<ExcludeMSBuildRuntime>true</ExcludeMSBuildRuntime>
</PropertyGroup>
<ItemGroup>
<Compile Remove="IntegrationScenarios\**" />
@ -30,19 +28,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="MoveAnalyzersToSourceUpdatersDir" AfterTargets="Build">
<PropertyGroup>
<SourceUpdateFilePath>$(OutputPath)SourceUpdaters\</SourceUpdateFilePath>
<ConfigUpdateFilePath>$(OutputPath)ConfigUpdaters\</ConfigUpdateFilePath>
</PropertyGroup>
<ItemGroup>
<SourceUpdateFiles Include="$(OutputPath)Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers.*" />
<SourceUpdateFiles Include="$(OutputPath)Microsoft.DotNet.UpgradeAssistant.Extensions.Default.CSharp.CodeFixes.*" />
<ConfigUpdateFiles Include="$(OutputPath)Microsoft.DotNet.UpgradeAssistant.Extensions.Default.ConfigUpdaters.*" />
</ItemGroup>
<Message Importance="normal" Text="Moving source updaters to $(SourceUpdateFilePath)" />
<Copy SourceFiles="@(SourceUpdateFiles)" DestinationFolder="$(SourceUpdateFilePath)" ContinueOnError="true" />
<Message Importance="normal" Text="Moving config updaters to $(ConfigUpdateFilePath)" />
<Copy SourceFiles="@(ConfigUpdateFiles)" DestinationFolder="$(ConfigUpdateFilePath)" ContinueOnError="true" />
</Target>
</Project>