Skip to content
This repository was archived by the owner on Feb 23, 2021. It is now read-only.

Commit cae1d5a

Browse files
author
N. Taylor Mullen
committed
Enable TagHelper resolution on unbuilt projects for non-desktop package only requests.
- Desktop requests would require that the tool be booted in desktop, therefore we can't resolve TagHelpers without dispatching in some form. If a packages entire graph is package based then we can ensure that a call to `AssemblyLoadContext.Load` returns loaded bits from the .nuget/packages folder on the current box. - Can't easily automate tests due to lack of end-to-end infrastructure dictated by #62. #70
1 parent c415aa1 commit cae1d5a

5 files changed

Lines changed: 163 additions & 12 deletions

File tree

src/Microsoft.AspNetCore.Razor.Tools/Internal/AssemblyTagHelperDescriptorResolver.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Globalization;
7-
using Microsoft.AspNetCore.Razor;
87
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
98
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
10-
using Microsoft.AspNetCore.Razor.Tools;
119

1210
namespace Microsoft.AspNetCore.Razor.Tools.Internal
1311
{
@@ -17,8 +15,13 @@ public class AssemblyTagHelperDescriptorResolver
1715
private readonly TagHelperTypeResolver _tagHelperTypeResolver;
1816

1917
public AssemblyTagHelperDescriptorResolver()
18+
: this(new TagHelperTypeResolver())
2019
{
21-
_tagHelperTypeResolver = new TagHelperTypeResolver();
20+
}
21+
22+
public AssemblyTagHelperDescriptorResolver(TagHelperTypeResolver tagHelperTypeResolver)
23+
{
24+
_tagHelperTypeResolver = tagHelperTypeResolver;
2225
}
2326

2427
public static int DefaultProtocolVersion { get; } = 1;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if NETCOREAPP1_0
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Runtime.Loader;
11+
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
12+
using Microsoft.DotNet.Cli.Utils;
13+
using Microsoft.DotNet.InternalAbstractions;
14+
using Microsoft.DotNet.ProjectModel;
15+
using Microsoft.DotNet.ProjectModel.Graph;
16+
using Microsoft.DotNet.ProjectModel.Loader;
17+
using Microsoft.Extensions.CommandLineUtils;
18+
using NuGet.Frameworks;
19+
20+
namespace Microsoft.AspNetCore.Razor.Tools.Internal
21+
{
22+
public class PackageOnlyResolveTagHelpersRunCommand : ResolveTagHelpersRunCommand
23+
{
24+
private readonly TagHelperTypeResolver packageTagHelperTypeResolver;
25+
26+
public PackageOnlyResolveTagHelpersRunCommand(AssemblyLoadContext loadContext)
27+
{
28+
packageTagHelperTypeResolver = new PackageTagHelperTypeResolver(loadContext);
29+
}
30+
31+
protected override AssemblyTagHelperDescriptorResolver CreateDescriptorResolver() =>
32+
new AssemblyTagHelperDescriptorResolver(packageTagHelperTypeResolver);
33+
34+
public static bool TryPackageOnlyTagHelperResolution(
35+
CommandArgument assemblyNamesArgument,
36+
CommandOption protocolOption,
37+
CommandOption buildBasePathOption,
38+
CommandOption configurationOption,
39+
Project project,
40+
NuGetFramework framework,
41+
out int exitCode)
42+
{
43+
exitCode = 0;
44+
45+
if (framework.IsDesktop())
46+
{
47+
return false;
48+
}
49+
50+
var runtimeIdentifiers = RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers();
51+
var projectContext = new ProjectContextBuilder()
52+
.WithProject(project)
53+
.WithTargetFramework(framework)
54+
.WithRuntimeIdentifiers(runtimeIdentifiers)
55+
.Build();
56+
var configuration = configurationOption.Value() ?? Constants.DefaultConfiguration;
57+
58+
var projectRuntimeOutputPath = projectContext.GetOutputPaths(configuration, buildBasePathOption.Value())?.RuntimeOutputPath;
59+
var projectAssemblyName = project.GetCompilerOptions(framework, configuration).OutputName;
60+
var projectOutputAssembly = Path.Combine(projectRuntimeOutputPath, projectAssemblyName + ".dll");
61+
62+
if (File.Exists(projectOutputAssembly))
63+
{
64+
// There's already build output. Dispatch to build output; this ensures dependencies have been resolved.
65+
return false;
66+
}
67+
68+
var projectLibraries = projectContext.LibraryManager.GetLibraries();
69+
var libraryLookup = projectLibraries.ToDictionary(
70+
library => library.Identity.Name,
71+
library => library,
72+
StringComparer.Ordinal);
73+
74+
foreach (var assemblyName in assemblyNamesArgument.Values)
75+
{
76+
if (!IsPackageOnly(assemblyName, libraryLookup))
77+
{
78+
return false;
79+
}
80+
}
81+
82+
var loadContext = projectContext.CreateLoadContext();
83+
var runner = new PackageOnlyResolveTagHelpersRunCommand(loadContext)
84+
{
85+
AssemblyNamesArgument = assemblyNamesArgument,
86+
ProtocolOption = protocolOption
87+
};
88+
89+
exitCode = runner.OnExecute();
90+
91+
return true;
92+
}
93+
94+
private static bool IsPackageOnly(string libraryName, IDictionary<string, LibraryDescription> libraryLookup)
95+
{
96+
LibraryDescription library;
97+
if (!libraryLookup.TryGetValue(libraryName, out library) ||
98+
library.Identity.Type != LibraryType.Package)
99+
{
100+
return false;
101+
}
102+
103+
foreach (var dependency in library.Dependencies)
104+
{
105+
if (!IsPackageOnly(dependency.Name, libraryLookup))
106+
{
107+
return false;
108+
}
109+
}
110+
111+
return true;
112+
}
113+
114+
private class PackageTagHelperTypeResolver : TagHelperTypeResolver
115+
{
116+
private readonly AssemblyLoadContext _loadContext;
117+
118+
public PackageTagHelperTypeResolver(AssemblyLoadContext loadContext)
119+
{
120+
_loadContext = loadContext;
121+
}
122+
123+
protected override IEnumerable<TypeInfo> GetExportedTypes(AssemblyName assemblyName)
124+
{
125+
var assembly = _loadContext.LoadFromAssemblyName(assemblyName);
126+
var exportedTypes = assembly.ExportedTypes;
127+
var exportedTypeInfos = exportedTypes.Select(type => type.GetTypeInfo());
128+
129+
return exportedTypeInfos;
130+
}
131+
}
132+
}
133+
}
134+
#endif

src/Microsoft.AspNetCore.Razor.Tools/Internal/ResolveTagHelpersDispatchCommand.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
using System.Globalization;
66
using System.IO;
77
using System.Linq;
8-
using Microsoft.AspNetCore.Razor.Tools;
9-
using Microsoft.DotNet.Cli.Utils;
108
using Microsoft.DotNet.ProjectModel;
119
using Microsoft.Extensions.CommandLineUtils;
1210
using Microsoft.Extensions.Internal;
@@ -58,6 +56,21 @@ protected override int OnExecute()
5856
return 0;
5957
}
6058

59+
#if NETCOREAPP1_0
60+
int exitCode;
61+
if (PackageOnlyResolveTagHelpersRunCommand.TryPackageOnlyTagHelperResolution(
62+
AssemblyNamesArgument,
63+
ProtocolOption,
64+
BuildBasePathOption,
65+
ConfigurationOption,
66+
projectFile,
67+
framework,
68+
out exitCode))
69+
{
70+
return exitCode;
71+
}
72+
#endif
73+
6174
var dispatchArgs = new List<string>
6275
{
6376
CommandName,
@@ -125,7 +138,8 @@ private bool TryResolveFramework(
125138
}
126139
else
127140
{
128-
framework = availableFrameworks.First();
141+
// Prioritize non-desktop frameworks since they have the option of not dispatching to resolve TagHelpers.
142+
framework = availableFrameworks.FirstOrDefault(f => !f.IsDesktop()) ?? availableFrameworks.First();
129143
}
130144

131145
resolvedFramework = framework;

src/Microsoft.AspNetCore.Razor.Tools/Internal/ResolveTagHelpersRunCommand.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Globalization;
6-
using Microsoft.AspNetCore.Razor;
76
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
8-
using Microsoft.AspNetCore.Razor.Tools;
97

108
namespace Microsoft.AspNetCore.Razor.Tools.Internal
119
{
@@ -32,10 +30,8 @@ protected override int OnExecute()
3230
protocol = AssemblyTagHelperDescriptorResolver.DefaultProtocolVersion;
3331
}
3432

35-
var descriptorResolver = new AssemblyTagHelperDescriptorResolver()
36-
{
37-
ProtocolVersion = protocol
38-
};
33+
var descriptorResolver = CreateDescriptorResolver();
34+
descriptorResolver.ProtocolVersion = protocol;
3935

4036
var errorSink = new ErrorSink();
4137
var resolvedDescriptors = new List<TagHelperDescriptor>();
@@ -50,5 +46,8 @@ protected override int OnExecute()
5046

5147
return 0;
5248
}
49+
50+
protected virtual AssemblyTagHelperDescriptorResolver CreateDescriptorResolver() =>
51+
new AssemblyTagHelperDescriptorResolver();
5352
}
5453
}

src/Microsoft.AspNetCore.Razor.Tools/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"frameworks": {
4444
"netcoreapp1.0": {
4545
"dependencies": {
46+
"Microsoft.DotNet.ProjectModel.Loader": "1.0.0-*",
4647
"Microsoft.NETCore.App": {
4748
"type": "platform",
4849
"version": "1.0.0-*"

0 commit comments

Comments
 (0)