Exemplo n.º 1
0
        internal static void findDependencies(this PackageDef pkg, List <string> excludeAdd, List <AssemblyData> searchedFiles)
        {
            bool foundNew = false;

            var notFound = new HashSet <string>();

            // First update the pre-entered dependencies
            var installed = new Installation(Directory.GetCurrentDirectory()).GetPackages().Where(p => p.Name != pkg.Name).ToList();

            List <string> brokenPackageNames = new List <string>();

            List <AssemblyData> getPackageAssemblues(PackageDef pkgDef)
            {
                List <AssemblyData> output = new List <AssemblyData>();

                foreach (var f in pkgDef.Files)
                {
                    var asms = searchedFiles.Where(sf => PathUtils.AreEqual(f.FileName, sf.Location))
                               .Where(sf => IsDotNetAssembly(sf.Location)).ToList();
                    if (asms.Count == 0 && (Path.GetExtension(f.FileName).Equals(".dll", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(f.FileName).Equals(".exe", StringComparison.OrdinalIgnoreCase)))
                    {
                        if (File.Exists(f.FileName))
                        {
                            // If the pluginSearcher found assemblies that are located somewhere not expected by the package definition, the package might appear broken.
                            // But if the file found by the pluginSearcher is the same as the one expected by the package definition we should not count it as broken.
                            // This could cause a package to not be added as a dependencies.
                            // E.g. when debugging and the OpenTAP.Cli.dll is both in the root build dir and in "Packages/OpenTAP"
                            var asmsIdenticalFilename = searchedFiles.Where(sf => Path.GetFileName(f.FileName) == Path.GetFileName(sf.Location));
                            var asmsIdentical         = asmsIdenticalFilename.Where(sf => PathUtils.CompareFiles(f.FileName, sf.Location));
                            output.AddRange(asmsIdentical);
                            continue;
                        }

                        if (!brokenPackageNames.Contains(pkgDef.Name) && IsDotNetAssembly(f.FileName))
                        {
                            brokenPackageNames.Add(pkgDef.Name);
                            log.Warning($"Package '{pkgDef.Name}' is not installed correctly?  Referenced file '{f.FileName}' was not found.");
                        }
                    }
                    output.AddRange(asms);
                }
                return(output);
            };

            var packageAssemblies = new Memorizer <PackageDef, List <AssemblyData> >(getPackageAssemblues);

            // check versions of any hardcoded dependencies against what is currently installed
            foreach (PackageDependency dep in pkg.Dependencies)
            {
                var installedPackage = installed.FirstOrDefault(ip => ip.Name == dep.Name);
                if (installedPackage != null)
                {
                    if (dep.Version == null)
                    {
                        dep.Version = new VersionSpecifier(installedPackage.Version, VersionMatchBehavior.Compatible);
                        log.Info("A version was not specified for package dependency {0}. Using installed version ({1}).", dep.Name, dep.Version);
                    }
                    else
                    {
                        if (!dep.Version.IsCompatible(installedPackage.Version))
                        {
                            throw new ExitCodeException((int)PackageCreateAction.ExitCodes.PackageDependencyError, $"Installed version of {dep.Name} ({installedPackage.Version}) is incompatible with dependency specified in package definition ({dep.Version}).");
                        }
                    }
                }
                else
                {
                    throw new ExitCodeException((int)PackageCreateAction.ExitCodes.PackageDependencyError,
                                                $"Package dependency '{dep.Name}' specified in package definition is not installed. Please install a compatible version first.");
                }
            }

            // Find additional dependencies
            do
            {
                foundNew = false;

                // Find everything we already know about
                var offeredByDependencies = AssembliesOfferedBy(installed, pkg.Dependencies, false, packageAssemblies).ToList();
                var offeredByThis         = packageAssemblies[pkg]
                                            .Where(f => f != null)
                                            .ToList();

                var anyOffered = offeredByDependencies.Concat(offeredByThis).ToList();

                // Find our dependencies and subtract the above two lists
                var dependentAssemblyNames = pkg.Files
                                             .SelectMany(fs => fs.DependentAssemblies)
                                             .Where(r => r.Name != "mscorlib") // Special case. We should not bundle the framework assemblies.
                                             .Where(r => !anyOffered.Any(of => AssemblyRefUtils.IsCompatibleReference(of, r)))
                                             .Distinct().Where(x => !excludeAdd.Contains(x.Name)).ToList();

                // If there's anything left we start resolving
                if (dependentAssemblyNames.Any())
                {
                    // First look in installed packages
                    var packageCandidates = new Dictionary <PackageDef, int>();
                    foreach (var f in installed)
                    {
                        var candidateAsms = packageAssemblies[f].Where(asm => dependentAssemblyNames.Any(dep => (dep.Name == asm.Name))).ToList();

                        // Don't consider a package that only matches assemblies in the Dependencies subfolder
                        candidateAsms.RemoveAll(asm => asm.Location.Contains("Dependencies")); // TODO: less lazy check for Dependencies subfolder would be good.

                        if (candidateAsms.Count > 0)
                        {
                            packageCandidates[f] = candidateAsms.Count;
                        }
                    }

                    // Look at the most promising candidate (i.e. the one containing most assemblies with the same names as things we need)
                    PackageDef candidatePkg = packageCandidates.OrderByDescending(k => k.Value).FirstOrDefault().Key;

                    if (candidatePkg != null)
                    {
                        foreach (AssemblyData candidateAsm in packageAssemblies[candidatePkg])
                        {
                            var requiredAsm = dependentAssemblyNames.FirstOrDefault(dep => dep.Name == candidateAsm.Name);
                            if (requiredAsm != null)
                            {
                                if (OpenTap.Utils.Compatible(candidateAsm.Version, requiredAsm.Version))
                                {
                                    log.Info($"Satisfying assembly reference to {requiredAsm.Name} by adding dependency on package {candidatePkg.Name}");
                                    if (candidateAsm.Version != requiredAsm.Version)
                                    {
                                        log.Warning($"Version of {requiredAsm.Name} in {candidatePkg.Name} is different from the version referenced in this package ({requiredAsm.Version} vs {candidateAsm.Version}).");
                                        log.Warning($"Consider changing your version of {requiredAsm.Name} to {candidateAsm.Version} to match that in {candidatePkg.Name}.");
                                    }
                                    foundNew = true;
                                }
                                else
                                {
                                    var depender = pkg.Files.FirstOrDefault(f => f.DependentAssemblies.Contains(requiredAsm));
                                    if (depender == null)
                                    {
                                        log.Error($"This package require assembly {requiredAsm.Name} in version {requiredAsm.Version} while that assembly is already installed through package '{candidatePkg.Name}' in version {candidateAsm.Version}.");
                                    }
                                    else
                                    {
                                        log.Error($"{Path.GetFileName(depender.FileName)} in this package require assembly {requiredAsm.Name} in version {requiredAsm.Version} while that assembly is already installed through package '{candidatePkg.Name}' in version {candidateAsm.Version}.");
                                    }
                                    //log.Error($"Please align the version of {requiredAsm.Name} to ensure interoperability with package '{candidate.Key.Name}' or uninstall that package.");
                                    throw new ExitCodeException((int)PackageCreateAction.ExitCodes.AssemblyDependencyError,
                                                                $"Please align the version of {requiredAsm.Name} ({candidateAsm.Version} vs {requiredAsm.Version})  to ensure interoperability with package '{candidatePkg.Name}' or uninstall that package.");
                                }
                            }
                        }
                        if (foundNew)
                        {
                            log.Info("Adding dependency on package '{0}' version {1}", candidatePkg.Name, candidatePkg.Version);

                            PackageDependency pd = new PackageDependency(candidatePkg.Name, new VersionSpecifier(candidatePkg.Version, VersionMatchBehavior.Compatible));
                            pkg.Dependencies.Add(pd);
                        }
                    }
                    else
                    {
                        // No installed package can offer any of the remaining referenced assemblies.
                        // add them as payload in this package in the Dependencies subfolder
                        foreach (var unknown in dependentAssemblyNames)
                        {
                            var foundAsms = searchedFiles.Where(asm => (asm.Name == unknown.Name) && OpenTap.Utils.Compatible(asm.Version, unknown.Version)).ToList();
                            var foundAsm  = foundAsms.FirstOrDefault();

                            if (foundAsm != null)
                            {
                                AddFileDependencies(pkg, unknown, foundAsm);
                                packageAssemblies.Invalidate(pkg);
                                foundNew = true;
                            }
                            else if (!notFound.Contains(unknown.Name))
                            {
                                log.Debug("'{0}' could not be found in any of {1} searched assemblies, or is already added.", unknown.Name, searchedFiles.Count);
                                notFound.Add(unknown.Name);
                            }
                        }
                    }
                }
            }while (foundNew);
        }