PERF: Cache result of parsing .csproj files

--HG--
branch : 1.x
extra : transplant_source : %02%96%9A%B7%91%05%E1%1EF%03%5D%5C%EB%A0%95Q%13%F6B%9C
This commit is contained in:
Renaud Paquay
2011-05-09 11:03:20 -07:00
parent 0b4bf6276d
commit 03ddb80526
5 changed files with 62 additions and 38 deletions

View File

@@ -53,10 +53,10 @@ namespace Orchard.Tests.Environment.Loaders {
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj");
// Create duplicate source files (invalid situation in reality but easy enough to test)
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.IsAny<Stream>())).Returns(
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.IsAny<string>())).Returns(
new ProjectFileDescriptor { SourceFilenames = new[] { fileName1, fileName2, fileName1 } }); // duplicate file
IEnumerable<string> dependencies = extensionLoader.GetDependenciesAccessor(fileEntry.Name);
@@ -76,14 +76,15 @@ namespace Orchard.Tests.Environment.Loaders {
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj");
StubFileSystem.FileEntry fileEntry3 = stubFileSystem.CreateFileEntry("orchard.c.csjproj");
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj");
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csproj");
StubFileSystem.FileEntry fileEntry3 = stubFileSystem.CreateFileEntry("orchard.c.csproj");
// Project a reference b and c which share a file in common
// Result for project a
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry)))
_mockedStubProjectFileParser
.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<string>(virtualPath => virtualPath == "orchard.a.csproj")))
.Returns(
new ProjectFileDescriptor {
SourceFilenames = new[] { fileName1, fileName2 },
@@ -96,16 +97,16 @@ namespace Orchard.Tests.Environment.Loaders {
},
new ReferenceDescriptor {
ReferenceType = ReferenceType.Project,
SimpleName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
FullName = Path.GetFileNameWithoutExtension(fileEntry2.Name),
SimpleName = Path.GetFileNameWithoutExtension(fileEntry3.Name),
FullName = Path.GetFileNameWithoutExtension(fileEntry3.Name),
Path = fileEntry3.Name
}
}
});
// Result for project b and c
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream =>
((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2 || ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry3)))
_mockedStubProjectFileParser
.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<string>(virtualPath => (virtualPath == "~/orchard.b.csproj" || virtualPath == "~/orchard.c.csproj"))))
.Returns(
new ProjectFileDescriptor {
SourceFilenames = new[] { commonFileName }
@@ -116,14 +117,14 @@ namespace Orchard.Tests.Environment.Loaders {
Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates");
// Project files
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry3.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry2.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry3.Name)), Is.Not.Null);
// Individual source files
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName1)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName2)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(commonFileName)), Is.Not.Null);
}
[Test]
@@ -135,8 +136,8 @@ namespace Orchard.Tests.Environment.Loaders {
DynamicExtensionLoaderAccessor extensionLoader = _container.Resolve<DynamicExtensionLoaderAccessor>();
StubFileSystem stubFileSystem = _container.Resolve<StubFileSystem>();
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csjproj");
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csjproj");
StubFileSystem.FileEntry fileEntry = stubFileSystem.CreateFileEntry("orchard.a.csproj");
StubFileSystem.FileEntry fileEntry2 = stubFileSystem.CreateFileEntry("orchard.b.csproj");
StubFileSystem.DirectoryEntry directoryEntry = stubFileSystem.CreateDirectoryEntry("bin");
StubFileSystem.FileEntry fileEntry3 = directoryEntry.CreateFile("orchard.b.dll");
@@ -144,7 +145,8 @@ namespace Orchard.Tests.Environment.Loaders {
// Project a reference b and c which share a file in common
// Result for project a
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream => ((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry)))
_mockedStubProjectFileParser
.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<string>(virtualPath => virtualPath == "orchard.a.csproj")))
.Returns(
new ProjectFileDescriptor {
SourceFilenames = new[] { fileName1, fileName2 },
@@ -159,14 +161,15 @@ namespace Orchard.Tests.Environment.Loaders {
});
// Result for project b and c
_mockedStubProjectFileParser.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<Stream>(stream =>
((StubFileSystem.FileEntryReadStream)stream).FileEntry == fileEntry2)))
_mockedStubProjectFileParser
.Setup(stubProjectFileParser => stubProjectFileParser.Parse(It.Is<string>(virtualPath => virtualPath == "~/orchard.b.csproj")))
.Returns(
new ProjectFileDescriptor {
SourceFilenames = new[] { commonFileName }
});
_mockedDependenciesFolder.Setup(dependenciesFolder => dependenciesFolder.GetDescriptor(It.Is<string>(moduleName => moduleName == Path.GetDirectoryName(fileEntry2.Name))))
_mockedDependenciesFolder
.Setup(dependenciesFolder => dependenciesFolder.GetDescriptor(It.Is<string>(moduleName => moduleName == Path.GetDirectoryName(fileEntry2.Name))))
.Returns(
new DependencyDescriptor {
VirtualPath = Path.Combine(directoryEntry.Name, fileEntry3.Name)
@@ -177,14 +180,14 @@ namespace Orchard.Tests.Environment.Loaders {
Assert.That(dependencies.Count(), Is.EqualTo(6), "6 results should mean no duplicates");
// Project files
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileEntry2.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(Path.Combine(directoryEntry.Name, fileEntry3.Name))), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileEntry2.Name)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(Path.Combine(directoryEntry.Name, fileEntry3.Name))), Is.Not.Null);
// Individual source files
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName1)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(fileName2)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Equals(commonFileName)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName1)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(fileName2)), Is.Not.Null);
Assert.That(dependencies.FirstOrDefault(dep => dep.Contains(commonFileName)), Is.Not.Null);
}
internal class DynamicExtensionLoaderAccessor : DynamicExtensionLoader {

View File

@@ -48,8 +48,7 @@ namespace Orchard.Environment.Extensions.Compilers {
return;
try {
using (var stream = _virtualPathProvider.OpenFile(context.VirtualPath)) {
var projectFileDescriptor = _projectFileParser.Parse(stream);
var projectFileDescriptor = _projectFileParser.Parse(context.VirtualPath);
// Add source files
var directory = _virtualPathProvider.GetDirectoryName(context.VirtualPath);
@@ -100,7 +99,6 @@ namespace Orchard.Environment.Extensions.Compilers {
}
}
}
}
catch (Exception e) {
//Note: we need to embed the "e.Message" in the exception text because
// ASP.NET build manager "swallows" inner exceptions from this method.

View File

@@ -3,12 +3,38 @@ using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Orchard.Caching;
using Orchard.FileSystems.WebSite;
namespace Orchard.Environment.Extensions.Compilers {
public class DefaultProjectFileParser : IProjectFileParser {
private readonly IWebSiteFolder _webSiteFolder;
private readonly ICacheManager _cacheManager;
public DefaultProjectFileParser(IWebSiteFolder webSiteFolder, ICacheManager cacheManager) {
_webSiteFolder = webSiteFolder;
_cacheManager = cacheManager;
}
public ProjectFileDescriptor Parse(string virtualPath) {
return _cacheManager.Get(virtualPath,
ctx => {
ctx.Monitor(_webSiteFolder.WhenPathChanges(virtualPath));
string content = _webSiteFolder.ReadFile(virtualPath);
using (var reader = new StringReader(content)) {
return Parse(reader);
}
});
}
public ProjectFileDescriptor Parse(Stream stream) {
var document = XDocument.Load(XmlReader.Create(stream));
using (var reader = new StreamReader(stream)) {
return Parse(reader);
}
}
public ProjectFileDescriptor Parse(TextReader reader) {
var document = XDocument.Load(XmlReader.Create(reader));
return new ProjectFileDescriptor {
AssemblyName = GetAssemblyName(document),
SourceFilenames = GetSourceFilenames(document).ToArray(),

View File

@@ -2,6 +2,7 @@
namespace Orchard.Environment.Extensions.Compilers {
public interface IProjectFileParser {
ProjectFileDescriptor Parse(string virtualPath);
ProjectFileDescriptor Parse(Stream stream);
}
}

View File

@@ -101,8 +101,7 @@ namespace Orchard.Environment.Extensions.Loaders {
if (projectPath == null)
return Enumerable.Empty<ExtensionReferenceProbeEntry>();
using (var stream = _virtualPathProvider.OpenFile(projectPath)) {
var projectFile = _projectFileParser.Parse(stream);
var projectFile = _projectFileParser.Parse(projectPath);
return projectFile.References.Select(r => new ExtensionReferenceProbeEntry {
Descriptor = descriptor,
@@ -111,7 +110,6 @@ namespace Orchard.Environment.Extensions.Loaders {
VirtualPath = _virtualPathProvider.GetProjectReferenceVirtualPath(projectPath, r.SimpleName, r.Path)
});
}
}
public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) {
//Note: This is the same implementation as "PrecompiledExtensionLoader"
@@ -193,8 +191,7 @@ namespace Orchard.Environment.Extensions.Loaders {
private void AddDependencies(string projectPath, HashSet<string> currentSet) {
string basePath = _virtualPathProvider.GetDirectoryName(projectPath);
using (var stream = _virtualPathProvider.OpenFile(projectPath)) {
ProjectFileDescriptor projectFile = _projectFileParser.Parse(stream);
ProjectFileDescriptor projectFile = _projectFileParser.Parse(projectPath);
// Add source files
currentSet.UnionWith(projectFile.SourceFilenames.Select(f => _virtualPathProvider.Combine(basePath, f)));
@@ -236,7 +233,6 @@ namespace Orchard.Environment.Extensions.Loaders {
}
}
}
}
private string GetProjectPath(ExtensionDescriptor descriptor) {
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id,