diff --git a/.gitignore b/.gitignore
index 15217d4a09..a0265568eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,5 @@ global.json
BenchmarkDotNet.Artifacts/
Microsoft.VisualStudio.RazorExtension.nuget.props
Microsoft.VisualStudio.RazorExtension.nuget.targets
+msbuild.binlog
+msbuild.log
diff --git a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets
index 8ae440eb70..09ffa3e51a 100644
--- a/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets
+++ b/src/Microsoft.AspNetCore.Razor.Design/build/netstandard2.0/Microsoft.AspNetCore.Razor.Design.CodeGeneration.targets
@@ -25,9 +25,9 @@
-->
+ Condition="'@(RazorGenerateWithTargetPath)' != ''">
-
+
@@ -51,7 +51,7 @@
DependsOnTargets="Compile"
Inputs="$(MSBuildAllProjects);@(RazorReferencePath)"
Outputs="$(_RazorTagHelperInputCache)"
- Condition="'@(RazorGenerate)'!=''">
+ Condition="'@(RazorGenerateWithTargetPath)' != ''">
-
- <_RazorCoreCompileResourceInputs
- Include="@(RazorEmbeddedResource)"
- Condition="'%(RazorEmbeddedResource.WithCulture)'=='false' and '%(RazorEmbeddedResource.Type)'=='Non-Resx' " />
diff --git a/src/Microsoft.AspNetCore.Razor.Language/FileSystemRazorProject.cs b/src/Microsoft.AspNetCore.Razor.Language/FileSystemRazorProject.cs
index d6e623c4f1..d6bfa5596a 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/FileSystemRazorProject.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/FileSystemRazorProject.cs
@@ -8,7 +8,7 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
- internal class FileSystemRazorProject : RazorProject
+ internal class FileSystemRazorProject : RazorProjectFileSystem
{
public FileSystemRazorProject(string root)
{
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/AssemblyInfo.cs
index c97fe625b4..02e2547355 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/AssemblyInfo.cs
@@ -8,6 +8,8 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Language.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.TagHelperTool, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Test.Common, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Tools.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("rzc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs
index 13c56c698e..a785a39bca 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs
@@ -1870,6 +1870,34 @@ namespace Microsoft.AspNetCore.Razor.Language
internal static string FormatRazorLanguageVersion_InvalidVersion(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorLanguageVersion_InvalidVersion"), p0);
+ ///
+ /// File path '{0}' does not belong to the directory '{1}'.
+ ///
+ internal static string VirtualFileSystem_FileDoesNotBelongToDirectory
+ {
+ get => GetString("VirtualFileSystem_FileDoesNotBelongToDirectory");
+ }
+
+ ///
+ /// File path '{0}' does not belong to the directory '{1}'.
+ ///
+ internal static string FormatVirtualFileSystem_FileDoesNotBelongToDirectory(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("VirtualFileSystem_FileDoesNotBelongToDirectory"), p0, p1);
+
+ ///
+ /// The file path '{0}' is invalid. File path is the root relative path of the file starting with '/' and should not contain any '\' characters.
+ ///
+ internal static string VirtualFileSystem_InvalidRelativePath
+ {
+ get => GetString("VirtualFileSystem_InvalidRelativePath");
+ }
+
+ ///
+ /// The file path '{0}' is invalid. File path is the root relative path of the file starting with '/' and should not contain any '\' characters.
+ ///
+ internal static string FormatVirtualFileSystem_InvalidRelativePath(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("VirtualFileSystem_InvalidRelativePath"), p0);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs
index 83b86dc8b7..975b821ade 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectFileSystem.cs
@@ -5,5 +5,14 @@ namespace Microsoft.AspNetCore.Razor.Language
{
public abstract class RazorProjectFileSystem : RazorProject
{
+ ///
+ /// Create a Razor project based on a physical file system.
+ ///
+ /// The directory to root the file system at.
+ /// A
+ public static new RazorProjectFileSystem Create(string rootDirectoryPath)
+ {
+ return new FileSystemRazorProject(rootDirectoryPath);
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx
index 6413f1038b..195a9d791b 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx
+++ b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx
@@ -536,4 +536,10 @@ Instead, wrap the contents of the block in "{{}}":
The Razor language version '{0}' is unrecognized or not supported by this version of Razor.
+
+ File path '{0}' does not belong to the directory '{1}'.
+
+
+ The file path '{0}' is invalid. File path is the root relative path of the file starting with '/' and should not contain any '\' characters.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Razor.Language/VirtualRazorProjectFileSystem.cs b/src/Microsoft.AspNetCore.Razor.Language/VirtualRazorProjectFileSystem.cs
new file mode 100644
index 0000000000..bb9a75ee3a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Language/VirtualRazorProjectFileSystem.cs
@@ -0,0 +1,216 @@
+// 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.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Razor.Language
+{
+ internal class VirtualRazorProjectFileSystem : RazorProjectFileSystem
+ {
+ private readonly DirectoryNode Root = new DirectoryNode("/");
+
+ public override IEnumerable EnumerateItems(string basePath)
+ {
+
+ basePath = NormalizeAndEnsureValidPath(basePath);
+ var directory = Root.GetDirectory(basePath);
+ return directory?.EnumerateItems() ?? Enumerable.Empty();
+ }
+
+ public override RazorProjectItem GetItem(string path)
+ {
+ path = NormalizeAndEnsureValidPath(path);
+ return Root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path);
+ }
+
+ public void Add(RazorProjectItem projectItem)
+ {
+ if (projectItem == null)
+ {
+ throw new ArgumentNullException(nameof(projectItem));
+ }
+
+ var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath);
+ Root.AddFile(new FileNode(filePath, projectItem));
+ }
+
+ // Internal for testing
+ [DebuggerDisplay("{Path}")]
+ internal class DirectoryNode
+ {
+ public DirectoryNode(string path)
+ {
+ Path = path;
+ }
+
+ public string Path { get; }
+
+ public List Directories { get; } = new List();
+
+ public List Files { get; } = new List();
+
+ public void AddFile(FileNode fileNode)
+ {
+ var filePath = fileNode.Path;
+ if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
+ {
+ var message = Resources.FormatVirtualFileSystem_FileDoesNotBelongToDirectory(fileNode.Path, Path);
+ throw new InvalidOperationException(message);
+ }
+
+ // Look for the first / that appears in the path after the current directory path.
+ var directoryPath = GetDirectoryPath(filePath);
+ var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true);
+ Debug.Assert(directory != null);
+ directory.Files.Add(fileNode);
+ }
+
+ public DirectoryNode GetDirectory(string path)
+ {
+ if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
+ {
+ var message = Resources.FormatVirtualFileSystem_FileDoesNotBelongToDirectory(path, Path);
+ throw new InvalidOperationException(message);
+ }
+
+ return GetOrAddDirectory(this, path);
+ }
+
+ public IEnumerable EnumerateItems()
+ {
+ foreach (var file in Files)
+ {
+ yield return file.ProjectItem;
+ }
+
+ foreach (var directory in Directories)
+ {
+ foreach (var file in directory.EnumerateItems())
+ {
+ yield return file;
+ }
+ }
+ }
+
+ public RazorProjectItem GetItem(string path)
+ {
+ if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(Resources.FormatVirtualFileSystem_FileDoesNotBelongToDirectory(path, Path));
+ }
+
+ var directoryPath = GetDirectoryPath(path);
+ var directory = GetOrAddDirectory(this, directoryPath);
+ if (directory == null)
+ {
+ return null;
+ }
+
+ foreach (var file in directory.Files)
+ {
+ var filePath = file.Path;
+ var directoryLength = directory.Path.Length;
+
+ // path, filePath -> /Views/Home/Index.cshtml
+ // directory.Path -> /Views/Home/
+ // We only need to match the file name portion since we've already matched the directory segment.
+ if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ return file.ProjectItem;
+ }
+ }
+
+ return null;
+ }
+
+ private static string GetDirectoryPath(string path)
+ {
+ // /dir1/dir2/file.cshtml -> /dir1/dir2/
+ var fileNameIndex = path.LastIndexOf('/');
+ if (fileNameIndex == -1)
+ {
+ return path;
+ }
+
+ return path.Substring(0, fileNameIndex + 1);
+ }
+
+ private static DirectoryNode GetOrAddDirectory(
+ DirectoryNode directory,
+ string path,
+ bool createIfNotExists = false)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(path));
+ if (path[path.Length - 1] != '/')
+ {
+ path += '/';
+ }
+
+ int index;
+ while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length)
+ {
+ var subDirectory = FindSubDirectory(directory, path);
+
+ if (subDirectory == null)
+ {
+ if (createIfNotExists)
+ {
+ var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash
+ subDirectory = new DirectoryNode(directoryPath);
+ directory.Directories.Add(subDirectory);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ directory = subDirectory;
+ }
+
+ return directory;
+ }
+
+ private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path)
+ {
+ for (var i = 0; i < parentDirectory.Directories.Count; i++)
+ {
+ // ParentDirectory.Path -> /Views/Home/
+ // CurrentDirectory.Path -> /Views/Home/SubDir/
+ // Path -> /Views/Home/SubDir/MorePath/File.cshtml
+ // Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file.
+
+ var currentDirectory = parentDirectory.Directories[i];
+ var directoryPath = currentDirectory.Path;
+ var startIndex = parentDirectory.Path.Length;
+ var directoryNameLength = directoryPath.Length - startIndex;
+
+ if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ return currentDirectory;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ // Internal for testing
+ [DebuggerDisplay("{Path}")]
+ internal struct FileNode
+ {
+ public FileNode(string path, RazorProjectItem projectItem)
+ {
+ Path = path;
+ ProjectItem = projectItem;
+ }
+
+ public string Path { get; }
+
+ public RazorProjectItem ProjectItem { get; }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs b/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs
index 3b92bed574..c1df3cd431 100644
--- a/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tasks/DotnetToolTask.cs
@@ -69,9 +69,9 @@ namespace Microsoft.AspNetCore.Razor.Tasks
{
if (Debug)
{
+ Log.LogMessage(MessageImportance.High, "Waiting for debugger in pid: {0}", Process.GetCurrentProcess().Id);
while (!Debugger.IsAttached)
{
- Log.LogMessage(MessageImportance.High, "Waiting for debugger in pid: {0}", Process.GetCurrentProcess().Id);
Thread.Sleep(TimeSpan.FromSeconds(3));
}
}
@@ -174,5 +174,18 @@ namespace Microsoft.AspNetCore.Razor.Tasks
CommandLineUtilities.SplitCommandLineIntoArguments(responseFileCommands, removeHashComments: true);
return responseFileArguments.ToList();
}
+
+ protected override bool HandleTaskExecutionErrors()
+ {
+ if (!HasLoggedErrors)
+ {
+ var toolCommand = Path.GetFileNameWithoutExtension(ToolAssembly) + " " + Command;
+ // Show a slightly better error than the standard ToolTask message that says "dotnet" failed.
+ Log.LogError($"{toolCommand} exited with code {ExitCode}.");
+ return false;
+ }
+
+ return base.HandleTaskExecutionErrors();
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Tasks/RazorGenerate.cs b/src/Microsoft.AspNetCore.Razor.Tasks/RazorGenerate.cs
index 06ed7088e2..7389725f94 100644
--- a/src/Microsoft.AspNetCore.Razor.Tasks/RazorGenerate.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tasks/RazorGenerate.cs
@@ -1,28 +1,44 @@
// 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.IO;
using System.Text;
using Microsoft.Build.Framework;
-using Microsoft.CodeAnalysis.CommandLine;
namespace Microsoft.AspNetCore.Razor.Tasks
{
public class RazorGenerate : DotNetToolTask
{
+ private const string GeneratedOutput = "GeneratedOutput";
+ private const string TargetPath = "TargetPath";
+ private const string FullPath = "FullPath";
+
[Required]
- public string[] Sources { get; set; }
+ public ITaskItem[] Sources { get; set; }
[Required]
public string ProjectRoot { get; set; }
- [Required]
- public string OutputPath { get; set; }
-
[Required]
public string TagHelperManifest { get; set; }
internal override string Command => "generate";
+ protected override bool ValidateParameters()
+ {
+ for (var i = 0; i < Sources.Length; i++)
+ {
+ if (!EnsureRequiredMetadata(Sources[i], FullPath) ||
+ !EnsureRequiredMetadata(Sources[i], GeneratedOutput) ||
+ !EnsureRequiredMetadata(Sources[i], TargetPath))
+ {
+ return false;
+ }
+ }
+
+ return base.ValidateParameters();
+ }
+
protected override string GenerateResponseFileCommands()
{
var builder = new StringBuilder();
@@ -31,19 +47,37 @@ namespace Microsoft.AspNetCore.Razor.Tasks
for (var i = 0; i < Sources.Length; i++)
{
- builder.AppendLine(Sources[i]);
+ var input = Sources[i];
+ builder.AppendLine("-s");
+ builder.AppendLine(input.GetMetadata(FullPath));
+
+ builder.AppendLine("-r");
+ builder.AppendLine(input.GetMetadata(TargetPath));
+
+ builder.AppendLine("-o");
+ var outputPath = Path.Combine(ProjectRoot, input.GetMetadata(GeneratedOutput));
+ builder.AppendLine(outputPath);
}
builder.AppendLine("-p");
builder.AppendLine(ProjectRoot);
- builder.AppendLine("-o");
- builder.AppendLine(OutputPath);
-
builder.AppendLine("-t");
builder.AppendLine(TagHelperManifest);
return builder.ToString();
}
+
+ private bool EnsureRequiredMetadata(ITaskItem item, string metadataName)
+ {
+ var value = item.GetMetadata(metadataName);
+ if (string.IsNullOrEmpty(value))
+ {
+ Log.LogError($"Missing required metadata '{metadataName}' for '{item.ItemSpec}.");
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/CompositeRazorProjectFileSystem.cs b/src/Microsoft.AspNetCore.Razor.Tools/CompositeRazorProjectFileSystem.cs
new file mode 100644
index 0000000000..5102cf056b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Razor.Tools/CompositeRazorProjectFileSystem.cs
@@ -0,0 +1,45 @@
+// 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 Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.AspNetCore.Razor.Tools
+{
+ internal class CompositeRazorProjectFileSystem : RazorProjectFileSystem
+ {
+ public CompositeRazorProjectFileSystem(IReadOnlyList projects)
+ {
+ Projects = projects ?? throw new ArgumentNullException(nameof(projects));
+ }
+
+ public IReadOnlyList Projects { get; }
+
+ public override IEnumerable EnumerateItems(string basePath)
+ {
+ foreach (var project in Projects)
+ {
+ foreach (var result in project.EnumerateItems(basePath))
+ {
+ yield return result;
+ }
+ }
+ }
+
+ public override RazorProjectItem GetItem(string path)
+ {
+ RazorProjectItem razorProjectItem = null;
+ foreach (var project in Projects)
+ {
+ razorProjectItem = project.GetItem(path);
+ if (razorProjectItem != null && razorProjectItem.Exists)
+ {
+ return razorProjectItem;
+ }
+ }
+
+ return razorProjectItem;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/DebugMode.cs b/src/Microsoft.AspNetCore.Razor.Tools/DebugMode.cs
index 7d9815cd5f..5d38654c52 100644
--- a/src/Microsoft.AspNetCore.Razor.Tools/DebugMode.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tools/DebugMode.cs
@@ -16,9 +16,9 @@ namespace Microsoft.AspNetCore.Razor.Tools
{
args = args.Skip(1).ToArray();
+ Console.WriteLine("Waiting for debugger in pid: {0}", Process.GetCurrentProcess().Id);
while (!Debugger.IsAttached)
{
- Console.WriteLine("Waiting for debugger in pid: {0}", Process.GetCurrentProcess().Id);
Thread.Sleep(TimeSpan.FromSeconds(3));
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs b/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
index 41c935b8b1..9fdedccd93 100644
--- a/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
@@ -13,20 +13,30 @@ using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools
{
+ internal class Builder
+ {
+ public static Builder Make(CommandBase result) => null;
+
+ public static Builder Make(T result) => null;
+ }
+
internal class GenerateCommand : CommandBase
{
public GenerateCommand(Application parent)
: base(parent, "generate")
{
- Sources = Argument("sources", ".cshtml files to compile", multipleValues: true);
+ Sources = Option("-s", ".cshtml files to compile", CommandOptionType.MultipleValue);
+ Outputs = Option("-o", "Generated output file path", CommandOptionType.MultipleValue);
+ RelativePaths = Option("-r", "Relative path", CommandOptionType.MultipleValue);
ProjectDirectory = Option("-p", "project root directory", CommandOptionType.SingleValue);
- OutputDirectory = Option("-o", "output directory", CommandOptionType.SingleValue);
TagHelperManifest = Option("-t", "tag helper manifest file", CommandOptionType.SingleValue);
}
- public CommandArgument Sources { get; }
+ public CommandOption Sources { get; }
- public CommandOption OutputDirectory { get; }
+ public CommandOption Outputs { get; }
+
+ public CommandOption RelativePaths { get; }
public CommandOption ProjectDirectory { get; }
@@ -36,25 +46,30 @@ namespace Microsoft.AspNetCore.Razor.Tools
{
var result = ExecuteCore(
projectDirectory: ProjectDirectory.Value(),
- outputDirectory: OutputDirectory.Value(),
tagHelperManifest: TagHelperManifest.Value(),
- sources: Sources.Values.ToArray());
+ sources: Sources.Values,
+ outputs: Outputs.Values,
+ relativePaths: RelativePaths.Values);
return Task.FromResult(result);
}
protected override bool ValidateArguments()
{
- if (string.IsNullOrEmpty(OutputDirectory.Value()))
+ if (Sources.Values.Count == 0)
{
- Error.WriteLine($"{OutputDirectory.ValueName} not specified.");
+ Error.WriteLine($"{Sources.ValueName} should have at least one value.");
return false;
}
- if (Sources.Values.Count == 0)
+ if (Outputs.Values.Count != Sources.Values.Count)
{
- Error.WriteLine($"{Sources.Name} should have at least one value.");
- return false;
+ Error.WriteLine($"{Sources.ValueName} has {Sources.Values.Count}, but {Outputs.ValueName} has {Outputs.Values.Count}.");
+ }
+
+ if (RelativePaths.Values.Count != Sources.Values.Count)
+ {
+ Error.WriteLine($"{Sources.ValueName} has {Sources.Values.Count}, but {RelativePaths.ValueName} has {RelativePaths.Values.Count}.");
}
if (string.IsNullOrEmpty(ProjectDirectory.Value()))
@@ -65,10 +80,14 @@ namespace Microsoft.AspNetCore.Razor.Tools
return true;
}
- private int ExecuteCore(string projectDirectory, string outputDirectory, string tagHelperManifest, string[] sources)
+ private int ExecuteCore(
+ string projectDirectory,
+ string tagHelperManifest,
+ List sources,
+ List outputs,
+ List relativePaths)
{
tagHelperManifest = Path.Combine(projectDirectory, tagHelperManifest);
- outputDirectory = Path.Combine(projectDirectory, outputDirectory);
var tagHelpers = GetTagHelpers(tagHelperManifest);
@@ -79,10 +98,18 @@ namespace Microsoft.AspNetCore.Razor.Tools
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, });
});
- var templateEngine = new MvcRazorTemplateEngine(engine, RazorProject.Create(projectDirectory));
- var sourceItems = GetRazorFiles(projectDirectory, sources);
- var results = GenerateCode(templateEngine, sourceItems);
+ var inputItems = GetInputItems(projectDirectory, sources, outputs, relativePaths);
+ var compositeProject = new CompositeRazorProjectFileSystem(
+ new[]
+ {
+ GetVirtualRazorProjectSystem(inputItems),
+ RazorProjectFileSystem.Create(projectDirectory),
+ });
+
+ var templateEngine = new MvcRazorTemplateEngine(engine, compositeProject);
+
+ var results = GenerateCode(templateEngine, inputItems);
var success = true;
@@ -97,16 +124,30 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
}
- var viewFile = result.ViewFileInfo.ViewEnginePath.Substring(1);
- var outputFileName = Path.ChangeExtension(viewFile, ".cs");
-
- var outputFilePath = Path.Combine(outputDirectory, outputFileName);
+ var outputFilePath = result.InputItem.OutputPath;
File.WriteAllText(outputFilePath, result.CSharpDocument.GeneratedCode);
}
return success ? 0 : -1;
}
+ private VirtualRazorProjectFileSystem GetVirtualRazorProjectSystem(SourceItem[] inputItems)
+ {
+ var project = new VirtualRazorProjectFileSystem();
+ foreach (var item in inputItems)
+ {
+ var projectItem = new FileSystemRazorProjectItem(
+ basePath: "/",
+ filePath: item.FilePath,
+ relativePhysicalPath: item.RelativePhysicalPath,
+ file: new FileInfo(item.SourcePath));
+
+ project.Add(projectItem);
+ }
+
+ return project;
+ }
+
private IReadOnlyList GetTagHelpers(string tagHelperManifest)
{
if (!File.Exists(tagHelperManifest))
@@ -127,33 +168,27 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
}
- private List GetRazorFiles(string projectDirectory, string[] sources)
+ private SourceItem[] GetInputItems(string projectDirectory, List sources, List outputs, List relativePath)
{
- var trimLength = projectDirectory.EndsWith("/") ? projectDirectory.Length - 1 : projectDirectory.Length;
-
- var items = new List(sources.Length);
- for (var i = 0; i < sources.Length; i++)
+ var items = new SourceItem[sources.Count];
+ for (var i = 0; i < items.Length; i++)
{
- var fullPath = Path.Combine(projectDirectory, sources[i]);
- if (fullPath.StartsWith(projectDirectory, StringComparison.OrdinalIgnoreCase))
- {
- var viewEnginePath = fullPath.Substring(trimLength).Replace('\\', '/');
- items.Add(new SourceItem(fullPath, viewEnginePath));
- }
+ var outputPath = Path.Combine(projectDirectory, outputs[i]);
+ items[i] = new SourceItem(sources[i], outputs[i], relativePath[i]);
}
return items;
}
- private OutputItem[] GenerateCode(RazorTemplateEngine templateEngine, IReadOnlyList sources)
+ private OutputItem[] GenerateCode(RazorTemplateEngine templateEngine, SourceItem[] inputs)
{
- var outputs = new OutputItem[sources.Count];
+ var outputs = new OutputItem[inputs.Length];
Parallel.For(0, outputs.Length, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, i =>
{
- var source = sources[i];
+ var inputItem = inputs[i];
- var csharpDocument = templateEngine.GenerateCode(source.ViewEnginePath);
- outputs[i] = new OutputItem(source, csharpDocument);
+ var csharpDocument = templateEngine.GenerateCode(inputItem.FilePath);
+ outputs[i] = new OutputItem(inputItem, csharpDocument);
});
return outputs;
@@ -162,43 +197,37 @@ namespace Microsoft.AspNetCore.Razor.Tools
private struct OutputItem
{
public OutputItem(
- SourceItem viewFileInfo,
+ SourceItem inputItem,
RazorCSharpDocument cSharpDocument)
{
- ViewFileInfo = viewFileInfo;
+ InputItem = inputItem;
CSharpDocument = cSharpDocument;
}
- public SourceItem ViewFileInfo { get; }
+ public SourceItem InputItem { get; }
public RazorCSharpDocument CSharpDocument { get; }
}
private struct SourceItem
{
- public SourceItem(string fullPath, string viewEnginePath)
+ public SourceItem(string sourcePath, string outputPath, string physicalRelativePath)
{
- FullPath = fullPath;
- ViewEnginePath = viewEnginePath;
+ SourcePath = sourcePath;
+ OutputPath = outputPath;
+ RelativePhysicalPath = physicalRelativePath;
+ FilePath = '/' + physicalRelativePath
+ .Replace(Path.DirectorySeparatorChar, '/')
+ .Replace("//", "/");
}
- public string FullPath { get; }
+ public string SourcePath { get; }
- public string ViewEnginePath { get; }
+ public string OutputPath { get; }
- public Stream CreateReadStream()
- {
- // We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer
- // 0 causes constructor to throw
- var bufferSize = 1;
- return new FileStream(
- FullPath,
- FileMode.Open,
- FileAccess.Read,
- FileShare.ReadWrite,
- bufferSize,
- FileOptions.Asynchronous | FileOptions.SequentialScan);
- }
+ public string RelativePhysicalPath { get; }
+
+ public string FilePath { get; }
}
private class StaticTagHelperFeature : ITagHelperFeature
@@ -210,4 +239,4 @@ namespace Microsoft.AspNetCore.Razor.Tools
public IReadOnlyList GetDescriptors() => TagHelpers;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets
index 99109a78d4..fe80f16785 100644
--- a/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets
+++ b/src/Microsoft.NET.Sdk.Razor/build/netstandard2.0/Sdk.Razor.CurrentVersion.targets
@@ -31,6 +31,7 @@ Copyright (c) .NET Foundation. All rights reserved.
ResolveRazorGenerateInputs;
+ AssignRazorGenerateTargetPaths;
ResolveAssemblyReferenceRazorGenerateInputs;
_EnsureRazorCompilerReferenced;
ResolveTagHelperRazorGenerateInputs
@@ -48,6 +49,7 @@ Copyright (c) .NET Foundation. All rights reserved.
+ ResolveRazorEmbeddedResources
@@ -221,11 +223,17 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+
-
- $(RazorGenerateIntermediateOutputPath)%(RelativeDir)%(Filename).cs
-
+
+ $(RazorGenerateIntermediateOutputPath)$([System.IO.Path]::ChangeExtension('%(RazorGenerateWithTargetPath.TargetPath)', '.cs'))
+
@@ -249,6 +257,21 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+ /$([System.String]::Copy('%(RazorGenerateWithTargetPath.TargetPath)').Replace('\','/'))
+ Non-Resx
+ false
+
+
+
+ <_RazorCoreCompileResourceInputs
+ Include="@(RazorEmbeddedResource)"
+ Condition="'%(RazorEmbeddedResource.WithCulture)'=='false' and '%(RazorEmbeddedResource.Type)'=='Non-Resx' " />
+
+
+