19 readonly IModuleFinder _beforeFinder;
20 readonly ImmutableArray<ModuleConfiguration> _parents;
21 readonly IModuleFinder _afterFinder;
22 readonly
string? _targetPlatform;
23 readonly Dictionary<string, ModuleReference> _nameToReference =
new();
25 bool _haveAllAutomaticModules;
33 public ModuleResolver(IModuleFinder beforeFinder, ImmutableArray<ModuleConfiguration> parents, IModuleFinder afterFinder)
35 _beforeFinder = beforeFinder;
37 _afterFinder = afterFinder;
39 foreach (var parent
in _parents)
41 var value = parent.TargetPlatform;
42 if (value is not
null)
44 if (_targetPlatform is
null)
46 _targetPlatform = value;
50 if (value != _targetPlatform)
51 throw new ArgumentException($
"Parents have conflicting constraints on target platform: {_targetPlatform}, {value}.", nameof(parents));
69 var q =
new ArrayDeque<ModuleDescriptor>();
71 foreach (var root
in roots)
73 var mref = FindWithBeforeFinder(root);
76 if (FindInParent(root) is not
null)
79 mref = FindWithAfterFinder(root);
81 throw new FindException($
"Module {root} not found.");
85 q.InsertFirst(mref.Descriptor);
100 HashSet<ModuleDescriptor> Resolve(ArrayDeque<ModuleDescriptor> q)
102 var resolved =
new HashSet<ModuleDescriptor>();
104 while (q.IsEmpty ==
false)
106 var descriptor = q.RemoveFirst();
107 if (_nameToReference.ContainsKey(descriptor.Name) ==
false)
108 throw new InvalidOperationException();
111 if (descriptor.IsAutomatic && !_haveAllAutomaticModules)
113 foreach (var mref
in AddFoundAutomaticModules())
114 q.InsertLast(mref.Descriptor);
116 _haveAllAutomaticModules =
true;
120 foreach (var
requires in descriptor.Requires)
123 if ((
requires.Modifiers & ByteCode.ModuleRequiresFlag.StaticPhase) != 0)
126 var dn =
requires.Name;
129 var mref = FindWithBeforeFinder(dn);
132 if (FindInParent(dn) !=
null)
135 mref = FindWithAfterFinder(dn);
137 throw new FindException($
"Module {dn} not found, required by {descriptor}.");
140 if (_nameToReference.ContainsKey(dn) ==
false)
142 AddFoundModule(mref);
143 q.InsertLast(mref.Descriptor);
148 resolved.Add(descriptor);
158 HashSet<ModuleReference> AddFoundAutomaticModules()
160 var result =
new HashSet<ModuleReference>();
162 foreach (var mref
in FindAll())
164 var mn = mref.Descriptor.Name;
165 if (mref.Descriptor.IsAutomatic && !_nameToReference.ContainsKey(mn))
167 AddFoundModule(mref);
180 void AddFoundModule(ModuleReference mref)
182 _nameToReference.Add(mref.Descriptor.Name, mref);
190 internal ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> Finish(ModuleConfiguration cf)
194 var graph = MakeGraph(cf);
195 CheckExportSuppliers(graph);
204 var visited =
new HashSet<ModuleDescriptor>();
205 var visitedPath =
new Stack<ModuleDescriptor>();
206 var visitedPathSet =
new HashSet<ModuleDescriptor>();
208 foreach (var mref
in _nameToReference.Values)
209 Visit(mref.Descriptor, visited, visitedPath, visitedPathSet);
217 void Visit(ModuleDescriptor descriptor, HashSet<ModuleDescriptor> visited, Stack<ModuleDescriptor> visitedPath, HashSet<ModuleDescriptor> visitedPathSet)
219 if (visited.Contains(descriptor) ==
false)
221 var added = visitedPathSet.Add(descriptor);
223 throw new InvalidOperationException($
"Cycle detected: {CycleToString(descriptor, visitedPath)}.");
225 visitedPath.Push(descriptor);
228 foreach (var
requires in descriptor.Requires)
230 if (_nameToReference.TryGetValue(
requires.Name, out var mref))
232 var other = mref.Descriptor;
233 if (other != descriptor)
234 Visit(other, visited, visitedPath, visitedPathSet);
238 visitedPathSet.Remove(descriptor);
241 visited.Add(descriptor);
251 string CycleToString(ModuleDescriptor descriptor, Stack<ModuleDescriptor> visitedPath)
254 var l =
new List<ModuleDescriptor>(visitedPath);
256 var p = l.IndexOf(descriptor);
258 return string.Join(
" -> ", l.Skip(p).Select(i => i.Name));
266 foreach (var mref
in _nameToReference.Values)
282 ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> MakeGraph(ModuleConfiguration cf)
285 var g1 = ImmutableDictionary.CreateBuilder<ResolvedModule, ImmutableHashSet<ResolvedModule>>();
291 .SelectMany(i => i.All())
293 .SelectMany(c => c.Modules
294 .SelectMany(m1 => m1.Descriptor.Requires
295 .Where(r => (r.Modifiers & ModuleRequiresFlag.Transitive) != 0)
296 .Select(r => c.FindModule(r.Name))
297 .Where(m2 => m2 !=
null)
298 .ToDictionary(m2 => m1, m2 => m2)))
300 .ToDictionary(i => i.Key, i => i.Select(j => j.Value!).ToHashSet()!);
302 var nameToResolved =
new Dictionary<string, ResolvedModule>(_nameToReference.Count);
304 foreach (var mref
in _nameToReference.Values)
306 var desc = mref.Descriptor;
307 var name = mref.Descriptor.Name;
309 var m1 = GetOrAddResolvedModule(nameToResolved, name, cf, mref);
311 var reads = ImmutableHashSet.CreateBuilder<ResolvedModule>();
312 var requiresTransitive =
new HashSet<ResolvedModule>();
314 foreach (var
requires in desc.Requires)
316 var dn =
requires.Name;
319 if (_nameToReference.TryGetValue(dn, out var mref2))
321 m2 = GetOrAddResolvedModule(nameToResolved, dn, cf, mref2);
325 m2 = FindInParent(dn);
329 if (m2.Descriptor.IsAutomatic)
330 foreach (var d
in m2.Reads)
331 if (d.Descriptor.IsAutomatic)
337 if ((
requires.Modifiers & ModuleRequiresFlag.Transitive) != 0)
338 requiresTransitive.Add(m2);
342 if (desc.IsAutomatic)
344 foreach (var mref2
in _nameToReference.Values)
346 var desc2 = mref2.Descriptor;
347 var name2 = desc2.Name;
351 var m2 = GetOrAddResolvedModule(nameToResolved, name2, cf, mref2);
353 if (desc2.IsAutomatic)
354 requiresTransitive.Add(m2);
359 foreach (var parent
in _parents)
361 foreach (var m
in parent.All().SelectMany(i => i.Modules))
364 if (m.Reference.Descriptor.IsAutomatic)
365 requiresTransitive.Add(m);
370 g1.Add(m1, reads.ToImmutable());
371 g2.Add(m1, requiresTransitive!);
374 var staging =
new List<ResolvedModule>();
380 foreach (var e
in g1)
385 if (m1.Descriptor.IsAutomatic ==
false)
387 foreach (var m2
in e.Value)
388 if (g2.TryGetValue(m2, out var m2RequiresTransitive))
389 foreach (var m3
in m2RequiresTransitive)
390 if (e.Value.Contains(m3) ==
false)
393 if (staging.Count > 0)
395 foreach (var i
in staging)
406 return g1.ToImmutable();
417 ResolvedModule GetOrAddResolvedModule(Dictionary<string, ResolvedModule> map,
string name, ModuleConfiguration cf, ModuleReference mref)
419 if (map.TryGetValue(name, out var m) ==
false)
421 m =
new ResolvedModule(cf, mref);
442 void CheckExportSuppliers(ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> graph)
444 foreach (var e
in graph)
446 var desc1 = e.Key.Descriptor;
447 var name1 = desc1.Name;
450 var names =
new HashSet<string>();
454 var packageToExporter =
new Dictionary<string, ModuleDescriptor>();
457 var packages = desc1.Packages;
458 foreach (var pn
in packages)
459 packageToExporter.Add(pn, desc1);
462 foreach (var endpoint
in e.Value)
464 var desc2 = endpoint.Descriptor;
465 var name2 = desc2.Name;
467 if (desc2 != desc1 && names.Add(name2) ==
false)
470 throw new InvalidOperationException($
"Module {name1} reads another module named {name1}.");
472 throw new InvalidOperationException($
"Module {name1} reads more than on module named {name2}.");
475 if (desc2.IsAutomatic)
479 foreach (var source
in desc2.Packages)
480 if (TryAdd(packageToExporter, source, desc2, out var supplier) ==
false)
481 throw FailTwoSuppliers(desc1, source, desc2, supplier);
485 foreach (var export
in desc2.Exports)
487 if (export.IsQualified)
488 if (export.Targets.Contains(desc1.Name) ==
false)
491 if (TryAdd(packageToExporter, export.Source, desc2, out var supplier) ==
false)
492 throw FailTwoSuppliers(desc1, export.Source, desc2, supplier);
498 if (desc1.IsAutomatic ==
false)
501 foreach (var service
in desc1.Uses)
503 var pn = PackageName(service);
504 if (packageToExporter.ContainsKey(pn) ==
false && RequiresStaticMissingModule(desc1, e.Value) ==
false)
505 throw new ResolutionException($
"Module {desc1.Name} uses {service} but does not read a module that exports {pn} to {desc1.Name}.");
509 foreach (var provides
in desc1.Provides)
511 var pn = PackageName(provides.Service);
512 if (packageToExporter.ContainsKey(pn) ==
false && RequiresStaticMissingModule(desc1, e.Value) ==
false)
513 throw new ResolutionException($
"Module {desc1.Name} provides {provides.Service} but does not read a module that exports {pn} to {desc1.Name}.");
526 bool RequiresStaticMissingModule(ModuleDescriptor descriptor, ImmutableHashSet<ResolvedModule> reads)
528 var moduleNames = reads
532 foreach (var r
in descriptor.Requires)
533 if ((r.Modifiers & ByteCode.ModuleRequiresFlag.StaticPhase) != 0 && moduleNames.Contains(r.Name) ==
false)
536 foreach (var rm
in reads)
537 foreach (var r
in rm.Descriptor.Requires)
538 if ((r.Modifiers & ModuleRequiresFlag.StaticPhase) != 0 && (r.Modifiers & ModuleRequiresFlag.Transitive) != 0 && moduleNames.Contains(r.Name) ==
false)
549 ResolvedModule? FindInParent(
string mn)
551 foreach (var parent
in _parents)
552 if (parent.FindModule(mn) is { } m)
563 ModuleReference? FindWithBeforeFinder(
string mn)
565 return _beforeFinder.Find(mn);
573 ModuleReference? FindWithAfterFinder(
string mn)
575 return _afterFinder.Find(mn);
582 ImmutableHashSet<ModuleReference> FindAll()
584 var bModules = _beforeFinder.FindAll();
585 var aModules = _afterFinder.FindAll();
587 if (aModules.IsEmpty)
590 if (bModules.IsEmpty && _parents.IsEmpty)
593 var result = ImmutableHashSet.CreateBuilder<ModuleReference>();
594 foreach (var mref
in aModules)
595 if (_beforeFinder.Find(mref.Descriptor.Name) is
null && FindInParent(mref.Descriptor.Name) is
null)
598 return result.ToImmutable();
606 string PackageName(
string className)
608 int index = className.LastIndexOf(
'.');
609 return index == -1 ?
"" : className.Substring(0, index);
622 bool TryAdd<TKey, TValue>(IDictionary<TKey, TValue>
self, TKey key, TValue value, [MaybeNullWhen(
false)] out TValue existing)
624 if (
self.TryGetValue(key, out existing))
627 self.Add(key, value);
642 Exception FailTwoSuppliers(ModuleDescriptor descriptor,
string source, ModuleDescriptor supplier1, ModuleDescriptor supplier2)
644 if (supplier2 == descriptor)
647 supplier1 = supplier2;
651 if (supplier1 == descriptor)
653 return new ResolutionException($
"Module {descriptor.Name} contains package {source}, module {supplier2.Name} exports package {source} to {descriptor.Name}.");
657 return new ResolutionException($
"Modules {supplier1.Name} and {supplier2.Name} export package {source} to module {descriptor.Name}.");