Implement dynamic compilation of modules

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-06-03 17:00:28 -07:00
parent d24f4cf2ac
commit abe6c67b25
10 changed files with 345 additions and 40 deletions

View File

@@ -0,0 +1,21 @@
using System.Web.Compilation;
namespace Orchard.Environment.Extensions.Loaders {
public class CSharpExtensionBuildProvider : BuildProvider {
private readonly CompilerType _codeCompilerType;
public CSharpExtensionBuildProvider() {
_codeCompilerType = GetDefaultCompilerTypeForLanguage("C#");
}
public override CompilerType CodeCompilerType { get { return _codeCompilerType; } }
public override void GenerateCode(AssemblyBuilder assemblyBuilder) {
var virtualPathProvider = new AspNetVirtualPathProvider();
var compiler = new CSharpProjectMediumTrustCompiler(virtualPathProvider);
var aspNetAssemblyBuilder = new AspNetAssemblyBuilder(assemblyBuilder, this);
compiler.CompileProject(this.VirtualPath, aspNetAssemblyBuilder);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Compile a C# extension into an assembly given a directory location
/// </summary>
public class CSharpExtensionCompiler {
public CompilerResults CompileProject(string location) {
var codeProvider = CodeDomProvider.CreateProvider("cs");
var references = GetAssemblyReferenceNames();
var options = new CompilerParameters(references.ToArray());
var fileNames = GetSourceFileNames(location);
var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
return results;
}
private IEnumerable<string> GetAssemblyReferenceNames() {
return Enumerable.Distinct<string>(BuildManager.GetReferencedAssemblies()
.OfType<Assembly>()
.Select(x => x.Location)
.Where(x => !string.IsNullOrEmpty(x)));
}
private IEnumerable<string> GetSourceFileNames(string path) {
foreach (var file in Directory.GetFiles(path, "*.cs")) {
yield return file;
}
foreach (var folder in Directory.GetDirectories(path)) {
if (Path.GetFileName(folder).StartsWith("."))
continue;
foreach (var file in GetSourceFileNames(folder)) {
yield return file;
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Compile a C# extension into an assembly given a directory location
/// </summary>
public class CSharpProjectFullTrustCompiler {
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IBuildManager _buildManager;
public CSharpProjectFullTrustCompiler(IVirtualPathProvider virtualPathProvider, IBuildManager buildManager) {
_virtualPathProvider = virtualPathProvider;
_buildManager = buildManager;
}
/// <summary>
/// Compile a csproj file given its virtual path. Use the CSharp CodeDomProvider
/// class, which is only available in full trust.
/// </summary>
public CompilerResults CompileProject(string virtualPath) {
var codeProvider = CodeDomProvider.CreateProvider("cs");
var directory = _virtualPathProvider.GetDirectoryName(virtualPath);
using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
var descriptor = new CSharpProjectParser().Parse(stream);
var references = GetAssemblyReferenceNames();
var options = new CompilerParameters(references.ToArray());
var fileNames = descriptor.SourceFilenames
.Select(f => _virtualPathProvider.Combine(directory, f))
.Select(f => _virtualPathProvider.MapPath(f));
var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
return results;
}
}
private IEnumerable<string> GetAssemblyReferenceNames() {
return _buildManager.GetReferencedAssemblies()
.OfType<Assembly>()
.Where(a => !string.IsNullOrEmpty(a.Location))
.Select(a => a.Location)
.Distinct();
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.CodeDom;
using System.IO;
using System.Linq;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Compile a C# extension into an assembly given a directory location
/// </summary>
public class CSharpProjectMediumTrustCompiler {
private readonly IVirtualPathProvider _virtualPathProvider;
public CSharpProjectMediumTrustCompiler(IVirtualPathProvider virtualPathProvider) {
_virtualPathProvider = virtualPathProvider;
}
/// <summary>
/// Compile a csproj file given its virtual path, a build provider and an assembly builder.
/// This method works in medium trust.
/// </summary>
public void CompileProject(string virtualPath, IAssemblyBuilder assemblyBuilder) {
using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
var descriptor = new CSharpProjectParser().Parse(stream);
var directory = _virtualPathProvider.GetDirectoryName(virtualPath);
foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) {
assemblyBuilder.AddCodeCompileUnit(CreateCompileUnit(filename));
}
}
}
private CodeCompileUnit CreateCompileUnit(string virtualPath) {
var contents = GetContents(virtualPath);
var unit = new CodeSnippetCompileUnit(contents);
var physicalPath = _virtualPathProvider.MapPath(virtualPath);
if (!string.IsNullOrEmpty(physicalPath)) {
unit.LinePragma = new CodeLinePragma(physicalPath, 1);
}
return unit;
}
private string GetContents(string virtualPath) {
string contents;
using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
using (var reader = new StreamReader(stream)) {
contents = reader.ReadToEnd();
}
}
return contents;
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Orchard.Environment.Extensions.Loaders {
public class CSharpProjectDescriptor {
public IEnumerable<string> SourceFilenames { get; set; }
public IEnumerable<ReferenceDescriptor> References { get; set; }
}
public class ReferenceDescriptor {
public string AssemblyName { get; set; }
}
public class CSharpProjectParser {
public CSharpProjectDescriptor Parse(Stream stream) {
var document = XDocument.Load(XmlReader.Create(stream));
return new CSharpProjectDescriptor {
SourceFilenames = GetSourceFilenames(document),
References = GetReferences(document)
};
}
private IEnumerable<string> GetSourceFilenames(XDocument document) {
return document
.Elements(ns("Project"))
.Elements(ns("ItemGroup"))
.Elements(ns("Compile"))
.Attributes("Include")
.Select(c => c.Value);
}
private IEnumerable<ReferenceDescriptor> GetReferences(XDocument document) {
return document
.Elements(ns("Project"))
.Elements(ns("ItemGroup"))
.Elements(ns("Reference"))
.Attributes("Include")
.Select(c => new ReferenceDescriptor { AssemblyName = c.Value });
}
private static XName ns(string name) {
return XName.Get(name, "http://schemas.microsoft.com/developer/msbuild/2003");
}
}
}

View File

@@ -1,7 +1,5 @@
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
@@ -15,45 +13,57 @@ namespace Orchard.Environment.Extensions.Loaders {
if (HostingEnvironment.IsHosted == false)
return null;
var codeProvider = CodeDomProvider.CreateProvider("cs");
var references = GetAssemblyReferenceNames();
var options = new CompilerParameters(references.ToArray());
var locationPath = HostingEnvironment.MapPath(descriptor.Location);
var extensionPath = Path.Combine(locationPath, descriptor.Name);
var fileNames = GetSourceFileNames(extensionPath);
var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
return new ExtensionEntry {
Descriptor = descriptor,
Assembly = results.CompiledAssembly,
ExportedTypes = results.CompiledAssembly.GetExportedTypes(),
};
}
private IEnumerable<string> GetAssemblyReferenceNames() {
return BuildManager.GetReferencedAssemblies()
.OfType<Assembly>()
.Select(x => x.Location)
.Where(x => !string.IsNullOrEmpty(x))
.Distinct();
}
private IEnumerable<string> GetSourceFileNames(string path) {
foreach (var file in Directory.GetFiles(path, "*.cs")) {
yield return file;
}
foreach (var folder in Directory.GetDirectories(path)) {
if (Path.GetFileName(folder).StartsWith("."))
continue;
foreach (var file in GetSourceFileNames(folder)) {
yield return file;
// 1) Try to load the assembly directory
// This will look in the "App_Data/Dependencies" directory if
// the probing path is correctly configured in Web.config
{
try {
Assembly assembly = Assembly.Load(descriptor.Name);
return CreateExtensionEntry(descriptor, assembly);
}
catch (FileNotFoundException e) {
// The assembly is not in one of the probing directory,
// including "App_Data/Dependencies", we need to move on
// to other strageties
}
}
// 2) look for the assembly in the "Bin" directory
{
string modulePath = HostingEnvironment.MapPath(descriptor.Location);
modulePath = Path.Combine(modulePath, descriptor.Name);
string moduleBinary = Path.Combine(modulePath, "bin");
moduleBinary = Path.Combine(moduleBinary, descriptor.Name + ".dll");
if (File.Exists(moduleBinary)) {
// Copy file to dependencies directory
string dependenciesPath = HostingEnvironment.MapPath("~/App_Data/Dependencies");
if (!Directory.Exists(dependenciesPath)) {
Directory.CreateDirectory(dependenciesPath);
}
string destFile = Path.Combine(dependenciesPath, descriptor.Name + ".dll");
File.Copy(moduleBinary, destFile, true);
// then load the assembly
Assembly assembly = Assembly.Load(descriptor.Name);
return CreateExtensionEntry(descriptor, assembly);
}
}
// 3) look for the csproj in the module directory
{
string projfileName = Path.Combine(descriptor.Location, descriptor.Name);
projfileName = Path.Combine(projfileName, descriptor.Name + ".csproj").Replace('\\', '/');
var assembly = BuildManager.GetCompiledAssembly(projfileName);
return CreateExtensionEntry(descriptor, assembly);
}
}
private static ExtensionEntry CreateExtensionEntry(ExtensionDescriptor descriptor, Assembly assembly) {
return new ExtensionEntry {
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes(),
};
}
}
}

View File

@@ -0,0 +1,22 @@
using System.CodeDom;
using System.Web.Compilation;
namespace Orchard.Environment.Extensions.Loaders {
public interface IAssemblyBuilder {
void AddCodeCompileUnit(CodeCompileUnit compileUnit);
}
public class AspNetAssemblyBuilder : IAssemblyBuilder {
private readonly AssemblyBuilder _assemblyBuilder;
private readonly BuildProvider _buildProvider;
public AspNetAssemblyBuilder(AssemblyBuilder assemblyBuilder, BuildProvider buildProvider) {
_assemblyBuilder = assemblyBuilder;
_buildProvider = buildProvider;
}
public void AddCodeCompileUnit(CodeCompileUnit compileUnit) {
_assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit);
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Compilation;
namespace Orchard.Environment.Extensions.Loaders {
public interface IBuildManager {
IEnumerable<string> GetReferencedAssemblies();
}
public class AspNetBuildManager : IBuildManager {
public IEnumerable<string> GetReferencedAssemblies() {
return BuildManager.GetReferencedAssemblies().Cast<string>();
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Web.Hosting;
namespace Orchard.Environment.Extensions.Loaders {
public interface IVirtualPathProvider {
string GetDirectoryName(string virtualPath);
string Combine(params string[] paths);
Stream OpenFile(string virtualPath);
string MapPath(string virtualPath);
}
public class AspNetVirtualPathProvider : IVirtualPathProvider {
public string GetDirectoryName(string virtualPath) {
return Path.GetDirectoryName(virtualPath);
}
public string Combine(params string[] paths) {
return Path.Combine(paths).Replace('\\', '/');
}
public Stream OpenFile(string virtualPath) {
return VirtualPathProvider.OpenFile(virtualPath);
}
public string MapPath(string virtualPath) {
return HostingEnvironment.MapPath(virtualPath);
}
}
}

View File

@@ -119,6 +119,7 @@
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Yaml, Version=1.0.3370.39839, Culture=neutral, PublicKeyToken=187a3d240e44a135, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\yaml\Yaml.dll</HintPath>
@@ -135,6 +136,14 @@
<Compile Include="Data\DataModule.cs" />
<Compile Include="Data\Orderable.cs" />
<Compile Include="Environment\DefaultOrchardShell.cs" />
<Compile Include="Environment\Extensions\Loaders\CSharpExtensionBuildProvider.cs" />
<Compile Include="Environment\Extensions\Loaders\CSharpExtensionCompiler.cs" />
<Compile Include="Environment\Extensions\Loaders\CSharpProjectExtensionCompilerMediumTrust.cs" />
<Compile Include="Environment\Extensions\Loaders\CSharpProjectFullTrustCompiler.cs" />
<Compile Include="Environment\Extensions\Loaders\CSharpProjectParser.cs" />
<Compile Include="Environment\Extensions\Loaders\IAssemblyBuilder.cs" />
<Compile Include="Environment\Extensions\Loaders\IBuildManager.cs" />
<Compile Include="Environment\Extensions\Loaders\IVirtualPathProvider.cs" />
<Compile Include="Environment\IOrchardShell.cs" />
<Compile Include="Environment\OrchardStarter.cs" />
<Compile Include="Environment\State\DefaultProcessingEngine.cs" />