mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 11:44:58 +08:00
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:
@@ -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 };
|
||||
}
|
||||
|
@@ -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 };
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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; }
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user