Add support for dynamic extension dependencies

Suppose Module A is loaded through its pre-compiled version
Suppose Module B is loaded through its pre-compiled version
Suppose Module A depends on Module B

If the csproj of Module B is updated, Module B will be dynamically compiled.
We have to ensure Module A is also dynamically compiled, because Module A can't
reference the pre-compiled version of B anymore.

The way we enforce this is by asking every loader if they are compatible
with all module references of a module before picking it as the actual loader.
If a loader decides it's not compatible with the module references, the next
loader in order of priority will be considered.

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-06-30 23:41:44 -07:00
parent 5b89b6a78d
commit 606d1869ab
9 changed files with 120 additions and 50 deletions

View File

@@ -69,6 +69,10 @@ namespace Orchard.Tests.Environment.Extensions {
throw new NotImplementedException();
}
public bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
throw new NotImplementedException();
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
}

View File

@@ -68,6 +68,10 @@ namespace Orchard.Tests.Environment.Extensions {
throw new NotImplementedException();
}
public bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
throw new NotImplementedException();
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
}

View File

@@ -8,6 +8,7 @@ using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Utility;
namespace Orchard.Environment.Extensions {
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
@@ -69,45 +70,6 @@ namespace Orchard.Environment.Extensions {
Logger.Information("Done loading extensions.");
}
private ExtensionLoadingContext CreateLoadingContext() {
var availableExtensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList();
var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders
.Select(loader => loader.Probe(extension))
.Where(probe => probe != null))
.GroupBy(e => e.Descriptor.Name)
.ToDictionary(g => g.Key, g => g.AsEnumerable()
.OrderByDescending(probe => probe.LastModificationTimeUtc)
.ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase);
var deletedDependencies = previousDependencies
.Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Name, e.Name)))
.ToList();
// Collect references for all modules
var references =
availableExtensions
.SelectMany(extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension)))
.ToList();
var referencesByModule = references
.GroupBy(entry => entry.Descriptor.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
var referencesByName = references
.GroupBy(reference => reference.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
return new ExtensionLoadingContext {
AvailableExtensions = availableExtensions,
PreviousDependencies = previousDependencies,
DeletedDependencies = deletedDependencies,
AvailableExtensionsProbes = availableExtensionsProbes,
ReferencesByName = referencesByName,
ReferencesByModule = referencesByModule
};
}
private void ProcessExtension(ExtensionLoadingContext context, ExtensionDescriptor extension) {
var extensionProbes = context.AvailableExtensionsProbes.ContainsKey(extension.Name) ?
@@ -123,7 +85,21 @@ namespace Orchard.Environment.Extensions {
}
}
var activatedExtension = extensionProbes.FirstOrDefault();
var moduleReferences =
context.AvailableExtensions
.Where(e =>
context.ReferencesByModule.ContainsKey(extension.Name) &&
context.ReferencesByModule[extension.Name].Any(r => StringComparer.OrdinalIgnoreCase.Equals(e.Name, r.Name)));
var processedModuleReferences =
moduleReferences
.Where(e => context.ProcessedExtensions.ContainsKey(e.Name))
.Select(e => context.ProcessedExtensions[e.Name]);
var activatedExtension = extensionProbes
.Where(e => e.Loader.IsCompatibleWithReferences(extension, processedModuleReferences))
.FirstOrDefault();
var previousDependency = context.PreviousDependencies.Where(d => StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Name)).FirstOrDefault();
if (activatedExtension == null) {
@@ -151,6 +127,61 @@ namespace Orchard.Environment.Extensions {
References = references
});
}
// Keep track of which loader we use for every extension
// This will be needed for processing references from other dependent extensions
context.ProcessedExtensions.Add(extension.Name, activatedExtension);
}
private ExtensionLoadingContext CreateLoadingContext() {
var availableExtensions = _extensionManager
.AvailableExtensions()
.Where(d => d.ExtensionType == "Module")
.OrderBy(d => d.Name)
.ToList();
var previousDependencies = _dependenciesFolder.LoadDescriptors().ToList();
var availableExtensionsProbes = availableExtensions.SelectMany(extension => _loaders
.Select(loader => loader.Probe(extension))
.Where(probe => probe != null))
.GroupBy(e => e.Descriptor.Name)
.ToDictionary(g => g.Key, g => g.AsEnumerable()
.OrderByDescending(probe => probe.LastModificationTimeUtc)
.ThenBy(probe => probe.Loader.Order), StringComparer.OrdinalIgnoreCase);
var deletedDependencies = previousDependencies
.Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Name, e.Name)))
.ToList();
// Collect references for all modules
var references =
availableExtensions
.SelectMany(extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension)))
.ToList();
var referencesByModule = references
.GroupBy(entry => entry.Descriptor.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
var referencesByName = references
.GroupBy(reference => reference.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
var sortedAvailableExtensions =
availableExtensions.OrderByDependencies(
(item, dep) => referencesByModule.ContainsKey(item.Name) &&
referencesByModule[item.Name].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Name, r.Name)))
.ToList();
return new ExtensionLoadingContext {
AvailableExtensions = sortedAvailableExtensions,
PreviousDependencies = previousDependencies,
DeletedDependencies = deletedDependencies,
AvailableExtensionsProbes = availableExtensionsProbes,
ReferencesByName = referencesByName,
ReferencesByModule = referencesByModule
};
}
IEnumerable<DependencyReferenceDescriptor> ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) {
@@ -193,16 +224,16 @@ namespace Orchard.Environment.Extensions {
.OrderBy(e => e.LastWriteTimeUtc)
.ThenBy(e => e.Entry.Name).FirstOrDefault();
var probes = context.AvailableExtensionsProbes.ContainsKey(referenceName) ?
context.AvailableExtensionsProbes[referenceName] :
Enumerable.Empty<ExtensionProbeEntry>();
var bestProbe = probes.FirstOrDefault();
var bestProbe = context.ProcessedExtensions.ContainsKey(referenceName) ?
context.ProcessedExtensions[referenceName] :
null;
// Pick the best one of module vs binary
if (bestProbe != null && bestBinaryReference != null) {
if (bestProbe.LastModificationTimeUtc >= bestBinaryReference.LastWriteTimeUtc) {
bestBinaryReference = null;
} else {
}
else {
bestProbe = null;
}
}
@@ -222,8 +253,7 @@ namespace Orchard.Environment.Extensions {
}
// Activated the module ref
if (bestProbe != null)
{
if (bestProbe != null) {
activatedReferences.Add(new DependencyReferenceDescriptor {
LoaderName = bestProbe.Loader.Name,
Name = referenceName,

View File

@@ -8,14 +8,14 @@ using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions {
public class ExtensionLoadingContext {
public ExtensionLoadingContext() {
ProcessedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
ProcessedExtensions = new Dictionary<string, ExtensionProbeEntry>(StringComparer.OrdinalIgnoreCase);
ProcessedReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
DeleteActions = new List<Action>();
CopyActions = new List<Action>();
NewDependencies = new List<DependencyDescriptor>();
}
public ISet<string> ProcessedExtensions { get; private set; }
public IDictionary<string, ExtensionProbeEntry> ProcessedExtensions { get; private set; }
public ISet<string> ProcessedReferences { get; private set; }
public IList<DependencyDescriptor> NewDependencies { get; private set; }

View File

@@ -36,7 +36,9 @@ namespace Orchard.Environment.Extensions {
foreach (var descriptor in AvailableExtensions()) {
// Extensions that are Themes don't have buildable components.
if (String.Equals(descriptor.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)) {
yield return BuildEntry(descriptor);
var entry = BuildEntry(descriptor);
if (entry != null)
yield return entry;
}
}
}
@@ -159,6 +161,8 @@ namespace Orchard.Environment.Extensions {
if (entry != null)
return entry;
}
Logger.Warning("No suitable loader found for extension \"{0}\"", descriptor.Name);
return null;
}
}

View File

@@ -25,6 +25,10 @@ namespace Orchard.Environment.Extensions.Loaders {
return null;
}
public virtual bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
return true;
}
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
public ExtensionEntry Load(ExtensionDescriptor descriptor) {

View File

@@ -28,6 +28,7 @@ namespace Orchard.Environment.Extensions.Loaders {
Assembly LoadReference(DependencyReferenceDescriptor reference);
void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references);
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
ExtensionEntry Load(ExtensionDescriptor descriptor);

View File

@@ -142,6 +142,17 @@ namespace Orchard.Environment.Extensions.Loaders {
} );
}
public override bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
// A pre-compiled module is _not_ compatible with a dynamically loaded module
// because a pre-compiled module usually references a pre-compiled assembly binary
// which will have a different identity (i.e. name) from the dynamic module.
bool result = references.All(r => r.Loader.GetType() != typeof (DynamicExtensionLoader));
if (!result) {
Logger.Information("Extension \"{0}\" will not be loaded as pre-compiled extension because one or more referenced extension is dynamically compiled", extension.Name);
}
return result;
}
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
var assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath == null)

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.Logging;
@@ -64,6 +65,17 @@ namespace Orchard.Environment.Extensions.Loaders {
}
}
public override bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
// A pre-compiled module is _not_ compatible with a dynamically loaded module
// because a pre-compiled module usually references a pre-compiled assembly binary
// which will have a different identity (i.e. name) from the dynamic module.
bool result = references.All(r => r.Loader.GetType() != typeof(DynamicExtensionLoader));
if (!result) {
Logger.Information("Extension \"{0}\" will not be loaded as pre-compiled extension because one or more referenced extension is dynamically compiled", extension.Name);
}
return result;
}
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (!_assemblyProbingFolder.AssemblyExists(descriptor.Name))
return null;