private void CollectDependencies(string thisId, ref Dictionary <RestoredDependencyPair, SharedConfig> myDependencies, RestoredDependencyPair pair) { // pair contains simply a Dependency in most cases, but if it contains a version already, then we map that version to a SharedConfig // Null assertions if (pair is null || pair.Dependency is null) { throw new ArgumentNullException(nameof(pair), Resources.DependencyNull); } var d = pair.Dependency !; if (d.Id is null) { throw new ArgumentException(Resources.DependencyIdNull); } if (d.VersionRange is null) { throw new ArgumentException($"Dependency: {d.Id} {nameof(d.VersionRange)} is null!"); } if (thisId.Equals(d.Id, StringComparison.OrdinalIgnoreCase)) { throw new DependencyException($"Recursive dependency! Tried to get dependency: {d.Id}, but {thisId} matches {d.Id}!"); } // We want to convert our uri into a config file var depConfig = dependencyResolver.GetSharedConfig(pair); if (depConfig is null) { throw new ConfigException($"Could not find config for: {d.Id}! Range: {d.VersionRange}"); } // Then we want to check to ensure that the config file we have gotten is within our version if (depConfig.Config is null) { throw new ConfigException($"Confid is of an invalid format for: {d.Id} - No config!"); } if (depConfig.Config.Info is null) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No info!"); } if (string.IsNullOrEmpty(depConfig.Config.Info.Id)) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No Id!"); } // Check to make sure the config's version matches our dependency's version if (!depConfig.Config.Info.Id.Equals(d.Id, StringComparison.OrdinalIgnoreCase)) { throw new ConfigException($"Dependency and config have different ids! {d.Id} != {depConfig.Config.Info.Id}!"); } if (depConfig.Config.Info.Version is null) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No Version!"); } // If it isn't, we fail to match our dependencies, exit out. if (!d.VersionRange.IsSatisfied(depConfig.Config.Info.Version)) { throw new DependencyException($"Dependency unmet! Want: {d.VersionRange} got: {depConfig.Config.Info.Version} for: {d.Id}"); } if (pair.Version != null && pair.Version != depConfig.Config.Info.Version) { throw new ConfigException($"Wanted specific version: {pair.Version} but got: {depConfig.Config.Info.Version} for: {d.Id}"); } var toAdd = new RestoredDependencyPair { Dependency = d, Version = depConfig.Config.Info.Version }; if (!myDependencies.ContainsKey(toAdd)) { // We need to double check here, just to make sure we don't accidentally add when we literally have a potential match: if (myDependencies.Keys.FirstOrDefault(item => toAdd.Dependency.Id.Equals(item.Dependency !.Id, StringComparison.OrdinalIgnoreCase) && toAdd.Version == item.Version) is null) { // If there is no exactly matching key: // Add our mapping from dependency to config myDependencies.Add(toAdd, depConfig); } } // Otherwise, we iterate over all of the config's RESTORED dependencies // That is, all of the dependencies that we used to actually build this foreach (var innerD in depConfig.RestoredDependencies) { if (innerD.Dependency is null || innerD.Version is null) { throw new ConfigException($"A restored dependency in config for: {depConfig.Config.Info.Id} version: {depConfig.Config.Info.Version} has a null dependency or version property!"); } // For each of the config's dependencies, collect all of the restored dependencies for it, // if we have no RestoredDependencies that match the ID, VersionRange, and Version already (since those would be the same). CollectDependencies(thisId, ref myDependencies, innerD); // We can actually take it easy here, we only need to COLLECT our dependencies, we don't need to COLLAPSE them. } // When we are done, myDependencies should contain a mapping of ALL of our dependencies (recursively) mapped to their SharedConfigs. }
private string PrintRestoredDependency(RestoredDependencyPair pair) => $"{pair.Dependency.Id}: ({pair.Dependency.VersionRange}) --> {pair.Version}";
private async Task CollectDependencies(string thisId, Dictionary <RestoredDependencyPair, SharedConfig> myDependencies, RestoredDependencyPair pair) { // pair contains simply a Dependency in most cases, but if it contains a version already, then we map that version to a SharedConfig // Null assertions if (pair is null || pair.Dependency is null) { throw new ArgumentNullException(nameof(pair), Resources.DependencyNull); } var d = pair.Dependency !; if (d.Id is null) { throw new ArgumentException(Resources.DependencyIdNull); } if (d.VersionRange is null) { throw new ArgumentException($"Dependency: {d.Id} {nameof(d.VersionRange)} is null!"); } if (thisId.Equals(d.Id, StringComparison.OrdinalIgnoreCase)) { throw new DependencyException($"Recursive dependency! Tried to get dependency: {d.Id}, but {thisId} matches {d.Id}!"); } // We want to convert our uri into a config file var depConfig = await dependencyResolver.GetSharedConfig(pair).ConfigureAwait(false); if (depConfig is null) { throw new ConfigException($"Could not find config for: {d.Id}! Range: {d.VersionRange}"); } // Then we want to check to ensure that the config file we have gotten is within our version if (depConfig.Config is null) { throw new ConfigException($"Confid is of an invalid format for: {d.Id} - No config!"); } if (depConfig.Config.Info is null) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No info!"); } if (string.IsNullOrEmpty(depConfig.Config.Info.Id)) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No Id!"); } // Check to make sure the config's version matches our dependency's version if (!depConfig.Config.Info.Id.Equals(d.Id, StringComparison.OrdinalIgnoreCase)) { throw new ConfigException($"Dependency and config have different ids! {d.Id} != {depConfig.Config.Info.Id}!"); } if (depConfig.Config.Info.Version is null) { throw new ConfigException($"Config is of an invalid format for: {d.Id} - No Version!"); } // If it isn't, we fail to match our dependencies, exit out. if (!d.VersionRange.IsSatisfied(depConfig.Config.Info.Version)) { throw new DependencyException($"Dependency unmet! Want: {d.VersionRange} got: {depConfig.Config.Info.Version} for: {d.Id}"); } if (pair.Version != null && pair.Version != depConfig.Config.Info.Version) { throw new ConfigException($"Wanted specific version: {pair.Version} but got: {depConfig.Config.Info.Version} for: {d.Id}"); } var toAdd = new RestoredDependencyPair { Dependency = d, Version = depConfig.Config.Info.Version }; // Add to collapsed mapping, if the dep to add/config is not an override name that would be a duplicate var match = myDependencies.FirstOrDefault(sc => sc.Value.Config !.Info !.AdditionalData.TryGetValue("overrideSoName", out var val) && depConfig.Config !.Info !.AdditionalData.TryGetValue("overrideSoName", out var rhs) && val.GetString() == rhs.GetString()).Key; if (match is not null) { // If we have a matching overrideSoName, check our config vs. existing config. // If our config is higher, use that instead. if (depConfig.Config !.Info !.Version > myDependencies[match].Config !.Info !.Version) { myDependencies.Remove(match); match.Dependency = d; match.Version = depConfig.Config !.Info !.Version; myDependencies.Add(match, depConfig); } } else if (!myDependencies.ContainsKey(toAdd)) { // We need to double check here, just to make sure we don't accidentally add when we literally have a potential match: if (myDependencies.Keys.FirstOrDefault(item => toAdd.Dependency.Id.Equals(item.Dependency !.Id, StringComparison.OrdinalIgnoreCase) && toAdd.Version == item.Version) is null) { // If there is no exactly matching key: // Add our mapping from dependency to config myDependencies.Add(toAdd, depConfig); } } // Otherwise, we iterate over all of the config's RESTORED dependencies // That is, all of the dependencies that we used to actually build this foreach (var innerD in new List <RestoredDependencyPair>(depConfig.RestoredDependencies)) { if (innerD.Dependency is null || innerD.Version is null) { throw new ConfigException($"A restored dependency in config for: {depConfig.Config.Info.Id} version: {depConfig.Config.Info.Version} has a null dependency or version property!"); } // Skip private dependencies from resolving if (innerD.Dependency.AdditionalData.TryGetValue("private", out var isPrivate) && isPrivate.GetBoolean()) { // Console.WriteLine($"Skipping {innerD.Dependency.Id}"); // TODO: Does sc2ad approve of this? depConfig.RestoredDependencies.Remove(innerD); continue; } // For each of the config's dependencies, collect all of the restored dependencies for it, // if we have no RestoredDependencies that match the ID, VersionRange, and Version already (since those would be the same). await CollectDependencies(thisId, myDependencies, innerD).ConfigureAwait(false); // We can actually take it easy here, we only need to COLLECT our dependencies, we don't need to COLLAPSE them. } // When we are done, myDependencies should contain a mapping of ALL of our dependencies (recursively) mapped to their SharedConfigs. }