diff --git a/helpers/helperslib/Buildable.py b/helpers/helperslib/Buildable.py --- a/helpers/helperslib/Buildable.py +++ b/helpers/helperslib/Buildable.py @@ -204,7 +204,14 @@ project.dependencies.append( dependency ) # Return a list of dependencies of ourselves - def forProject(self, project, platform, includeSubDeps = True, checkDynamicDeps = True, currentlyKnown = []): + def forProject(self, project, platform, includeSubDeps = True, checkDynamicDeps = True): + self.dependenciesCache = {} + return self.forProjectRec(project, platform, includeSubDeps, checkDynamicDeps) + + def forProjectRec(self, project, platform, includeSubDeps = True, checkDynamicDeps = True): + if not project.virtualDependency and project in self.dependenciesCache: + return self.dependenciesCache[project] + # Initialise - let's get ready to parse the dependencies file ourDeps = finalDynamic = [] @@ -219,7 +226,7 @@ # Add the project level dependencies to the list of our dependencies # Then run the list of our dependencies against the project negations - ourDeps = self._processDependencyNegation( ourDeps + project.dependencies, project.negatedDeps ) + ourDeps = self._processDependencyNegation( project.dependencies, project.negatedDeps ) + ourDeps # Ensure the current project is not listed (due to a dynamic dependency for instance) ourDeps = [dep for dep in ourDeps if dep != project] @@ -231,23 +238,31 @@ # Determine which projects we need to resolve dependencies for # We process dynamic dependencies separately, as they can be recursive # We also skip any dependency which has been determined as being needed by a higher up iteration to avoid unnecessary resolving of dependencies - toLookup = set(ourDeps) - set(finalDynamic) - set(currentlyKnown) + toLookup = [x for x in ourDeps if x not in finalDynamic] for dependency in toLookup: - ourDeps = ourDeps + self.forProject(dependency, platform, includeSubDeps = True, currentlyKnown = ourDeps) + ourDeps = self.forProjectRec(dependency, platform, includeSubDeps = True) + ourDeps # Process dynamic dependencies here # We won't look at other dynamic dependency rules when we do this # This is slightly limiting, but it is otherwise possible to end up in a dependency resolution loop - dynamicLookup = set(ourDeps) & set(finalDynamic) + dynamicLookup = ourDeps + finalDynamic for dependency in dynamicLookup: - ourDeps = ourDeps + self.forProject(dependency, platform, includeSubDeps = True, checkDynamicDeps = False, currentlyKnown = ourDeps) + ourDeps = self.forProjectRec(dependency, platform, includeSubDeps = True, checkDynamicDeps = False) + ourDeps # Re-ensure the current project is not listed and that they are not virtual # Dynamic dependency resolution of sub-dependencies may have re-added it ourDeps = [dep for dep in ourDeps if dep != project and not dep.virtualDependency] # Ensure we don't have any duplicates - return list(set(ourDeps)) + deps = self.unique(ourDeps) + + self.dependenciesCache[project] = deps + + return deps + + def unique(self, sequence): + seen = set() + return [x for x in sequence if not (x in seen or seen.add(x))] def _resolveDynamicDependencies(self, project, dynamicDeps): # Go over the dynamic dependencies list we have and see if we match @@ -264,7 +279,7 @@ def _processDependencyNegation(self, dependentProjects, negatedProjects): # Remove any dependencies which have been negated - return list( set(dependentProjects) - set(negatedProjects) ) + return [x for x in dependentProjects if not x in negatedProjects] # Class which can parse the product-definitions.yaml file # This allows us to determine what build combinations we need to support