Add a subsystem for detecting versions

This adds the beginning of an API for detecting versions from the
project. We will flesh out this API more when we do tooling
extensiblity.
This commit is contained in:
Ryan Nowak 2017-09-05 19:36:26 -07:00
parent 0155cf2c73
commit ca844afe5a
8 changed files with 426 additions and 0 deletions

View File

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// This is hardcoded for now. A more complete design would fan out to a list of providers.
internal class DefaultProjectExtensibilityConfigurationFactory : ProjectExtensibilityConfigurationFactory
{
private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
private const string RazorV1AssemblyName = "Microsoft.AspNetCore.Razor";
private const string RazorV2AssemblyName = "Microsoft.AspNetCore.Razor.Language";
// Using MaxValue here so that we ignore patch and build numbers. We only want to compare major/minor.
private static readonly Version MaxSupportedRazorVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
private static readonly Version MaxSupportedMvcVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
private static readonly Version DefaultRazorVersion = new Version(2, 0, 0, 0);
private static readonly Version DefaultMvcVersion = new Version(2, 0, 0, 0);
public async override Task<ProjectExtensibilityConfiguration> GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken))
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken);
return GetConfiguration(compilation.ReferencedAssemblyNames);
}
// internal/separate for testing.
internal ProjectExtensibilityConfiguration GetConfiguration(IEnumerable<AssemblyIdentity> references)
{
// Avoiding ToDictionary here because we don't want a crash if there is a duplicate name.
var assemblies = new Dictionary<string, AssemblyIdentity>();
foreach (var assembly in references)
{
assemblies[assembly.Name] = assembly;
}
// First we look for the V2+ Razor Assembly. If we find this then its version is the correct Razor version.
AssemblyIdentity razorAssembly;
if (assemblies.TryGetValue(RazorV2AssemblyName, out razorAssembly))
{
if (razorAssembly.Version == null || razorAssembly.Version > MaxSupportedRazorVersion)
{
// This is a newer Razor version than we know, treat it as a fallback case.
razorAssembly = null;
}
}
else if (assemblies.TryGetValue(RazorV1AssemblyName, out razorAssembly))
{
// This assembly only counts as the 'Razor' assembly if it's a version lower than 2.0.0.
if (razorAssembly.Version == null || razorAssembly.Version >= new Version(2, 0, 0, 0))
{
razorAssembly = null;
}
}
AssemblyIdentity mvcAssembly;
if (assemblies.TryGetValue(MvcAssemblyName, out mvcAssembly))
{
if (mvcAssembly.Version == null || mvcAssembly.Version > MaxSupportedMvcVersion)
{
// This is a newer MVC version than we know, treat it as a fallback case.
mvcAssembly = null;
}
}
if (razorAssembly != null && mvcAssembly != null)
{
// This means we've definitely found a supported Razor version and an MVC version.
return new MvcExtensibilityConfiguration(
ProjectExtensibilityConfigurationKind.ApproximateMatch,
new ProjectExtensibilityAssembly(razorAssembly),
new ProjectExtensibilityAssembly(mvcAssembly));
}
// If we get here it means we didn't find everything, so we have to guess.
if (razorAssembly == null || razorAssembly.Version == null)
{
razorAssembly = new AssemblyIdentity(RazorV2AssemblyName, DefaultRazorVersion);
}
if (mvcAssembly == null || mvcAssembly.Version == null)
{
mvcAssembly = new AssemblyIdentity(MvcAssemblyName, DefaultMvcVersion);
}
return new MvcExtensibilityConfiguration(
ProjectExtensibilityConfigurationKind.Fallback,
new ProjectExtensibilityAssembly(razorAssembly),
new ProjectExtensibilityAssembly(mvcAssembly));
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
[Shared]
[ExportLanguageServiceFactory(typeof(ProjectExtensibilityConfigurationFactory), RazorLanguage.Name)]
internal class DefaultProjectExtensibilityConfigurationFactoryFactory : ILanguageServiceFactory
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
return new DefaultProjectExtensibilityConfigurationFactory();
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class MvcExtensibilityConfiguration : ProjectExtensibilityConfiguration
{
public MvcExtensibilityConfiguration(
ProjectExtensibilityConfigurationKind kind,
ProjectExtensibilityAssembly razorAssembly,
ProjectExtensibilityAssembly mvcAssembly)
{
if (razorAssembly == null)
{
throw new ArgumentNullException(nameof(razorAssembly));
}
if (mvcAssembly == null)
{
throw new ArgumentNullException(nameof(mvcAssembly));
}
Kind = kind;
RazorAssembly = razorAssembly;
MvcAssembly = mvcAssembly;
Assemblies = new[] { RazorAssembly, MvcAssembly, };
}
public override IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
public override ProjectExtensibilityConfigurationKind Kind { get; }
public override ProjectExtensibilityAssembly RazorAssembly { get; }
public ProjectExtensibilityAssembly MvcAssembly { get; }
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal sealed class ProjectExtensibilityAssembly
{
public ProjectExtensibilityAssembly(AssemblyIdentity identity)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
Identity = identity;
}
public AssemblyIdentity Identity { get; }
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class ProjectExtensibilityConfiguration
{
public abstract IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
public abstract ProjectExtensibilityConfigurationKind Kind { get; }
public abstract ProjectExtensibilityAssembly RazorAssembly { get; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class ProjectExtensibilityConfigurationFactory : ILanguageService
{
public abstract Task<ProjectExtensibilityConfiguration> GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
/// <summary>
/// Describes how closely the configuration of Razor tooling matches the actual project dependencies.
/// </summary>
internal enum ProjectExtensibilityConfigurationKind
{
ApproximateMatch,
Fallback,
}
}

View File

@ -0,0 +1,193 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public class DefaultProjectExtensibilityConfigurationFactoryTest
{
[Theory]
[InlineData("1.0.0.0", "1.0.0.0")]
[InlineData("1.1.0.0", "1.1.0.0")]
[InlineData("2.0.0.0", "2.0.0.0")]
[InlineData("2.0.2.0", "2.0.2.0")]
public void GetConfiguration_FindsSupportedConfiguration_ForNewRazor(string razorVersion, string mvcVersion)
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version(razorVersion)),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
}
[Theory]
[InlineData("1.0.0.0", "1.0.0.0")]
[InlineData("1.1.0.0", "1.1.0.0")]
[InlineData("1.9.9.9", "2.0.0.0")] // MVC version is ignored
public void GetConfiguration_FindsSupportedConfiguration_ForOldRazor(string razorVersion, string mvcVersion)
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version(razorVersion)),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_RazorVersion_NewAssemblyWinsOverOld()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_RazorVersion_OldAssemblyIgnoredPastV1()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_NoRazorVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_UnsupportedRazorVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("3.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_NoMvcVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_UnsupportedMvcVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
}
}