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:
Родитель
550aa94351
Коммит
6a77f069d4
|
@ -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>
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче