Example #1
0
        /// <summary>
        /// Determine whether the current set of artifacts in the project matches the set of
        /// specified dependencies.
        /// </summary>
        /// <param name="destDirectory">Directory where dependencies are located in the
        /// project.  If this parameter is null, a dictionary of required dependencies is
        /// always returned.</param>
        /// <param name="explodeAar">Delegate that determines whether a dependency should be
        /// exploded.  If a dependency is currently exploded but shouldn't be according to this
        /// delegate, the dependency is deleted.</param>
        /// <returns>null if all dependencies are present, dictionary of all required dependencies
        /// otherwise.</returns>
        public Dictionary <string, Dependency> DependenciesPresent(string destDirectory,
                                                                   ExplodeAar explodeAar = null)
        {
            Dictionary <string, Dependency> dependencyMap =
                LoadDependencies(true, keepMissing: true, findCandidates: true);

            // If a destination directory was specified, determine whether the dependencies
            // referenced by dependencyMap differ to what is present in the project.  If they
            // are the same, we can skip this entire method.
            if (destDirectory != null)
            {
                if (DependenciesEqual(GetCurrentDependencies(dependencyMap, destDirectory,
                                                             explodeAar: explodeAar,
                                                             repoPaths: repositoryPaths).Keys,
                                      dependencyMap.Keys))
                {
                    Log("All dependencies up to date.", verbose: true);
                    return(null);
                }
            }
            return(dependencyMap);
        }
Example #2
0
        /// <summary>
        /// Performs the resolution process.  This determines the versions of the
        /// dependencies that should be used.  Transitive dependencies are also
        /// processed.
        /// </summary>
        /// <returns>The dependencies.  The key is the "versionless" key of the dependency.
        /// </returns>
        /// <param name="useLatest">If set to <c>true</c> use latest version of a conflicting
        /// dependency.
        /// if <c>false</c> a ResolutionException is thrown in the case of a conflict.</param>
        /// <param name="destDirectory">Directory dependencies will be copied to using
        /// CopyDependencies().</param>
        /// <param name="explodeAar">Delegate that determines whether a dependency should be
        /// exploded.  If a dependency is currently exploded but shouldn't be according to this
        /// delegate, the dependency is deleted.</param>
        public Dictionary <string, Dependency> ResolveDependencies(
            bool useLatest, string destDirectory = null, ExplodeAar explodeAar = null)
        {
            List <Dependency> unresolved = new List <Dependency>();

            Dictionary <string, Dependency> candidates = new Dictionary <string, Dependency>();

            Dictionary <string, Dependency> dependencyMap =
                DependenciesPresent(destDirectory, explodeAar: explodeAar);

            if (dependencyMap == null)
            {
                return(candidates);
            }

            // Set of each versioned dependencies for each version-less dependency key.
            // e.g if foo depends upon bar, map[bar] = {foo}.
            var reverseDependencyTree = new Dictionary <string, HashSet <string> >();
            var warnings = new HashSet <string>();

            // All dependencies are added to the "unresolved" list.
            foreach (var dependency in dependencyMap.Values)
            {
                unresolved.Add(FindCandidate(dependency));
            }

            do
            {
                Dictionary <string, Dependency> nextUnresolved =
                    new Dictionary <string, Dependency>();

                foreach (Dependency dep in unresolved)
                {
                    var currentDep = dep;
                    // Whether the dependency has been resolved and therefore should be removed
                    // from the unresolved list.
                    bool removeDep = true;

                    // check for existing candidate
                    Dependency candidate;
                    Dependency newCandidate;
                    if (candidates.TryGetValue(currentDep.VersionlessKey, out candidate))
                    {
                        if (currentDep.IsAcceptableVersion(candidate.BestVersion))
                        {
                            removeDep = true;

                            // save the most restrictive dependency in the
                            //  candidate
                            if (currentDep.IsNewer(candidate))
                            {
                                candidates[currentDep.VersionlessKey] = currentDep;
                            }
                        }
                        else
                        {
                            // in general, we need to iterate
                            removeDep = false;

                            // refine one or both dependencies if they are
                            // non-concrete.
                            bool possible = false;
                            if (currentDep.Version.Contains("+") && candidate.IsNewer(currentDep))
                            {
                                possible = currentDep.RefineVersionRange(candidate);
                            }

                            // only try if the candidate is less than the depenceny
                            if (candidate.Version.Contains("+") && currentDep.IsNewer(candidate))
                            {
                                possible = possible || candidate.RefineVersionRange(currentDep);
                            }

                            if (possible)
                            {
                                // add all the dependency constraints back to make
                                // sure all are met.
                                foreach (Dependency d in dependencyMap.Values)
                                {
                                    if (d.VersionlessKey == candidate.VersionlessKey)
                                    {
                                        if (!nextUnresolved.ContainsKey(d.Key))
                                        {
                                            nextUnresolved.Add(d.Key, d);
                                        }
                                    }
                                }
                            }
                            else if (!possible && useLatest)
                            {
                                // Reload versions of the dependency has they all have been
                                // removed.
                                newCandidate = (currentDep.IsNewer(candidate) ?
                                                currentDep : candidate);
                                newCandidate = newCandidate.HasPossibleVersions ? newCandidate :
                                               FindCandidate(newCandidate);
                                candidates[newCandidate.VersionlessKey] = newCandidate;
                                currentDep = newCandidate;
                                removeDep  = true;
                                // Due to a dependency being included via multiple modules we track
                                // whether a warning has already been reported and make sure it's
                                // only reported once.
                                if (!warnings.Contains(currentDep.VersionlessKey))
                                {
                                    // If no parents of this dependency are found the app
                                    // must have specified the dependency.
                                    string requiredByString =
                                        currentDep.VersionlessKey + " required by (this app)";
                                    // Print dependencies to aid debugging.
                                    var dependenciesMessage = new List <string>();
                                    dependenciesMessage.Add("Found dependencies:");
                                    dependenciesMessage.Add(requiredByString);
                                    foreach (var kv in reverseDependencyTree)
                                    {
                                        string requiredByMessage =
                                            String.Format(
                                                "{0} required by ({1})",
                                                kv.Key,
                                                String.Join(
                                                    ", ",
                                                    (new List <string>(kv.Value)).ToArray()));
                                        dependenciesMessage.Add(requiredByMessage);
                                        if (kv.Key == currentDep.VersionlessKey)
                                        {
                                            requiredByString = requiredByMessage;
                                        }
                                    }
                                    Log(String.Join("\n", dependenciesMessage.ToArray()));
                                    Log(String.Format(
                                            "WARNING: No compatible versions of {0}, will try using " +
                                            "the latest version {1}", requiredByString,
                                            currentDep.BestVersion));
                                    warnings.Add(currentDep.VersionlessKey);
                                }
                            }
                            else if (!possible)
                            {
                                throw new ResolutionException("Cannot resolve " +
                                                              currentDep + " and " + candidate);
                            }
                        }
                    }
                    else
                    {
                        candidate = FindCandidate(currentDep);
                        if (candidate != null)
                        {
                            candidates.Add(candidate.VersionlessKey, candidate);
                            removeDep = true;
                        }
                        else
                        {
                            throw new ResolutionException("Cannot resolve " +
                                                          currentDep);
                        }
                    }

                    // If the dependency has been found.
                    if (removeDep)
                    {
                        // Add all transitive dependencies to resolution list.
                        foreach (Dependency d in GetDependencies(currentDep))
                        {
                            if (!nextUnresolved.ContainsKey(d.Key))
                            {
                                Log("For " + currentDep.Key + " adding dep " + d.Key,
                                    verbose: true);
                                HashSet <string> parentNames;
                                if (!reverseDependencyTree.TryGetValue(d.VersionlessKey,
                                                                       out parentNames))
                                {
                                    parentNames = new HashSet <string>();
                                }
                                parentNames.Add(currentDep.Key);
                                reverseDependencyTree[d.VersionlessKey] = parentNames;
                                nextUnresolved.Add(d.Key, d);
                            }
                        }
                    }
                    else
                    {
                        if (!nextUnresolved.ContainsKey(currentDep.Key))
                        {
                            nextUnresolved.Add(currentDep.Key, currentDep);
                        }
                    }
                }

                unresolved.Clear();
                unresolved.AddRange(nextUnresolved.Values);
                nextUnresolved.Clear();
            }while (unresolved.Count > 0);

            return(candidates);
        }
Example #3
0
        /// <summary>
        /// Get the current set of dependencies referenced by the project.
        /// </summary>
        /// <param name="dependencies">Dependencies to search for in the specified
        /// directory.</param>
        /// <param name="destDirectory">Directory where dependencies are located in the
        /// project.</param>
        /// <param name="explodeAar">Delegate that determines whether a dependency should be
        /// exploded.  If a dependency is currently exploded but shouldn't be according to this
        /// delegate, the dependency is deleted.</param>
        /// <param name="repoPaths">Set of additional repo paths to search for the
        /// dependencies.</param>
        /// <returns>Dictionary indexed by Dependency.Key where each item is a tuple of
        /// Dependency instance and the path to the dependency in destDirectory.</returns>
        public static Dictionary <string, KeyValuePair <Dependency, string> > GetCurrentDependencies(
            Dictionary <string, Dependency> dependencies, string destDirectory,
            ExplodeAar explodeAar = null, List <string> repoPaths = null)
        {
            var currentDependencies = new Dictionary <string, KeyValuePair <Dependency, string> >();

            if (dependencies.Count == 0)
            {
                return(currentDependencies);
            }

            // Copy the set of dependencies.
            var transitiveDependencies = new Dictionary <string, Dependency>(dependencies);

            // Expand set of transitive dependencies into the dictionary of dependencies.
            foreach (var rootDependency in dependencies.Values)
            {
                foreach (var transitiveDependency in GetDependencies(rootDependency, repoPaths))
                {
                    transitiveDependencies[transitiveDependency.Key] = transitiveDependency;
                }
            }
            // TODO(smiles): Need a callback that queries Unity's asset DB rather than touching
            // the filesystem here.
            string[] filesInDestDir = Directory.GetFileSystemEntries(destDirectory);
            foreach (var path in filesInDestDir)
            {
                // Ignore Unity's .meta files.
                if (path.EndsWith(MetaExtension))
                {
                    continue;
                }

                string filename = Path.GetFileName(path);
                // Strip the package extension from filenames.  Directories generated from
                // unpacked AARs do not have extensions.
                bool   pathIsDirectory          = Directory.Exists(path);
                string filenameWithoutExtension = pathIsDirectory ? filename :
                                                  Path.GetFileNameWithoutExtension(filename);

                foreach (var dep in transitiveDependencies.Values)
                {
                    // Get the set of artifacts matching artifact-*.
                    // The "-" is important to distinguish art-1.0.0 from artifact-1.0.0
                    // (or base- and basement-).
                    var match = System.Text.RegularExpressions.Regex.Match(
                        filenameWithoutExtension, String.Format("^{0}-.*", dep.Artifact));
                    if (match.Success)
                    {
                        // Extract the version from the filename.
                        // dep.Artifact is the name of the package (prefix)
                        // The regular expression extracts the version number from the filename
                        // handling filenames like foo-1.2.3-alpha.
                        match = System.Text.RegularExpressions.Regex.Match(
                            filenameWithoutExtension.Substring(
                                dep.Artifact.Length + 1), "^([0-9.]+)");
                        if (match.Success)
                        {
                            bool reportDependency = true;
                            // If the AAR is exploded and it should not be, delete it and do not
                            // report this dependency.
                            if (pathIsDirectory && explodeAar != null)
                            {
                                string aarFile = dep.BestVersionArtifact;
                                if (aarFile != null && !explodeAar(aarFile))
                                {
                                    DeleteExistingFileOrDirectory(path,
                                                                  includeMetaFiles: true);
                                    reportDependency = false;
                                }
                            }
                            if (reportDependency)
                            {
                                string     artifactVersion = match.Groups[1].Value;
                                Dependency currentDep      = new Dependency(
                                    dep.Group, dep.Artifact, artifactVersion,
                                    packageIds: dep.PackageIds, repositories: dep.Repositories);
                                // Add the artifact version so BestVersion == Version.
                                currentDep.AddVersion(currentDep.Version);
                                currentDependencies[currentDep.Key] =
                                    new KeyValuePair <Dependency, string>(currentDep, path);
                            }
                            break;
                        }
                    }
                }
            }
            return(currentDependencies);
        }