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

View File

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

View File

@@ -3,12 +3,38 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Orchard.Caching;
using Orchard.FileSystems.WebSite;
namespace Orchard.Environment.Extensions.Compilers { namespace Orchard.Environment.Extensions.Compilers {
public class DefaultProjectFileParser : IProjectFileParser { 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) { 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 { return new ProjectFileDescriptor {
AssemblyName = GetAssemblyName(document), AssemblyName = GetAssemblyName(document),
SourceFilenames = GetSourceFilenames(document).ToArray(), SourceFilenames = GetSourceFilenames(document).ToArray(),

View File

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

View File

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