mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||||
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
|
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
|
||||||
}
|
}
|
||||||
|
@@ -68,6 +68,10 @@ namespace Orchard.Tests.Environment.Extensions {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||||
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
|
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ using Orchard.FileSystems.Dependencies;
|
|||||||
using Orchard.FileSystems.VirtualPath;
|
using Orchard.FileSystems.VirtualPath;
|
||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
|
using Orchard.Utility;
|
||||||
|
|
||||||
namespace Orchard.Environment.Extensions {
|
namespace Orchard.Environment.Extensions {
|
||||||
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
|
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
|
||||||
@@ -69,45 +70,6 @@ namespace Orchard.Environment.Extensions {
|
|||||||
Logger.Information("Done loading 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) {
|
private void ProcessExtension(ExtensionLoadingContext context, ExtensionDescriptor extension) {
|
||||||
|
|
||||||
var extensionProbes = context.AvailableExtensionsProbes.ContainsKey(extension.Name) ?
|
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();
|
var previousDependency = context.PreviousDependencies.Where(d => StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Name)).FirstOrDefault();
|
||||||
|
|
||||||
if (activatedExtension == null) {
|
if (activatedExtension == null) {
|
||||||
@@ -151,6 +127,61 @@ namespace Orchard.Environment.Extensions {
|
|||||||
References = references
|
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) {
|
IEnumerable<DependencyReferenceDescriptor> ProcessExtensionReferences(ExtensionLoadingContext context, ExtensionProbeEntry activatedExtension) {
|
||||||
@@ -193,16 +224,16 @@ namespace Orchard.Environment.Extensions {
|
|||||||
.OrderBy(e => e.LastWriteTimeUtc)
|
.OrderBy(e => e.LastWriteTimeUtc)
|
||||||
.ThenBy(e => e.Entry.Name).FirstOrDefault();
|
.ThenBy(e => e.Entry.Name).FirstOrDefault();
|
||||||
|
|
||||||
var probes = context.AvailableExtensionsProbes.ContainsKey(referenceName) ?
|
var bestProbe = context.ProcessedExtensions.ContainsKey(referenceName) ?
|
||||||
context.AvailableExtensionsProbes[referenceName] :
|
context.ProcessedExtensions[referenceName] :
|
||||||
Enumerable.Empty<ExtensionProbeEntry>();
|
null;
|
||||||
var bestProbe = probes.FirstOrDefault();
|
|
||||||
|
|
||||||
// Pick the best one of module vs binary
|
// Pick the best one of module vs binary
|
||||||
if (bestProbe != null && bestBinaryReference != null) {
|
if (bestProbe != null && bestBinaryReference != null) {
|
||||||
if (bestProbe.LastModificationTimeUtc >= bestBinaryReference.LastWriteTimeUtc) {
|
if (bestProbe.LastModificationTimeUtc >= bestBinaryReference.LastWriteTimeUtc) {
|
||||||
bestBinaryReference = null;
|
bestBinaryReference = null;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
bestProbe = null;
|
bestProbe = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,8 +253,7 @@ namespace Orchard.Environment.Extensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Activated the module ref
|
// Activated the module ref
|
||||||
if (bestProbe != null)
|
if (bestProbe != null) {
|
||||||
{
|
|
||||||
activatedReferences.Add(new DependencyReferenceDescriptor {
|
activatedReferences.Add(new DependencyReferenceDescriptor {
|
||||||
LoaderName = bestProbe.Loader.Name,
|
LoaderName = bestProbe.Loader.Name,
|
||||||
Name = referenceName,
|
Name = referenceName,
|
||||||
|
@@ -8,14 +8,14 @@ using Orchard.FileSystems.Dependencies;
|
|||||||
namespace Orchard.Environment.Extensions {
|
namespace Orchard.Environment.Extensions {
|
||||||
public class ExtensionLoadingContext {
|
public class ExtensionLoadingContext {
|
||||||
public ExtensionLoadingContext() {
|
public ExtensionLoadingContext() {
|
||||||
ProcessedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
ProcessedExtensions = new Dictionary<string, ExtensionProbeEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
ProcessedReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
ProcessedReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
DeleteActions = new List<Action>();
|
DeleteActions = new List<Action>();
|
||||||
CopyActions = new List<Action>();
|
CopyActions = new List<Action>();
|
||||||
NewDependencies = new List<DependencyDescriptor>();
|
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 ISet<string> ProcessedReferences { get; private set; }
|
||||||
|
|
||||||
public IList<DependencyDescriptor> NewDependencies { get; private set; }
|
public IList<DependencyDescriptor> NewDependencies { get; private set; }
|
||||||
|
@@ -36,7 +36,9 @@ namespace Orchard.Environment.Extensions {
|
|||||||
foreach (var descriptor in AvailableExtensions()) {
|
foreach (var descriptor in AvailableExtensions()) {
|
||||||
// Extensions that are Themes don't have buildable components.
|
// Extensions that are Themes don't have buildable components.
|
||||||
if (String.Equals(descriptor.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)) {
|
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)
|
if (entry != null)
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Warning("No suitable loader found for extension \"{0}\"", descriptor.Name);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,10 @@ namespace Orchard.Environment.Extensions.Loaders {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||||
|
|
||||||
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||||
|
@@ -28,6 +28,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
|||||||
Assembly LoadReference(DependencyReferenceDescriptor reference);
|
Assembly LoadReference(DependencyReferenceDescriptor reference);
|
||||||
void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
|
void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
|
||||||
void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
|
void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
|
||||||
|
bool IsCompatibleWithReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references);
|
||||||
|
|
||||||
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
|
||||||
ExtensionEntry Load(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) {
|
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||||
var assemblyPath = GetAssemblyPath(descriptor);
|
var assemblyPath = GetAssemblyPath(descriptor);
|
||||||
if (assemblyPath == null)
|
if (assemblyPath == null)
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Orchard.Environment.Extensions.Models;
|
using Orchard.Environment.Extensions.Models;
|
||||||
using Orchard.FileSystems.Dependencies;
|
using Orchard.FileSystems.Dependencies;
|
||||||
using Orchard.Logging;
|
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) {
|
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||||
if (!_assemblyProbingFolder.AssemblyExists(descriptor.Name))
|
if (!_assemblyProbingFolder.AssemblyExists(descriptor.Name))
|
||||||
return null;
|
return null;
|
||||||
|
Reference in New Issue
Block a user