check_blocking_repos.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/env python
  2. from __future__ import print_function
  3. import argparse
  4. import sys
  5. import rosdistro
  6. from rosdistro.dependency_walker import DependencyWalker
  7. def is_released(repository, dist_file):
  8. return repository in dist_file.repositories and \
  9. dist_file.repositories[repository].release_repository is not None and \
  10. dist_file.repositories[repository].release_repository.version is not None
  11. parser = argparse.ArgumentParser(
  12. description='Get unreleased repos and their dependencies.',
  13. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  14. parser.add_argument(
  15. '--rosdistro', metavar='ROS_DISTRO',
  16. help='The ROS distribution to check packages for')
  17. # If not specified, check for all repositories in the previous distribution
  18. parser.add_argument(
  19. '--repositories',
  20. metavar='REPOSITORY_NAME', nargs='*',
  21. help='Unreleased repositories to check dependencies for')
  22. parser.add_argument(
  23. '--depth',
  24. metavar='depth', type=int,
  25. help='Maxmium depth to crawl the dependency tree')
  26. parser.add_argument(
  27. '--comparison-rosdistro',
  28. metavar='ROS_DISTRO',
  29. dest='comparison',
  30. help='The rosdistro with which to compare')
  31. args = parser.parse_args()
  32. distro_key = args.rosdistro
  33. repo_names_argument = args.repositories
  34. prev_distro_key = None
  35. index = rosdistro.get_index(rosdistro.get_index_url())
  36. valid_distro_keys = index.distributions.keys()
  37. valid_distro_keys.sort()
  38. if distro_key is None:
  39. distro_key = valid_distro_keys[-1]
  40. # Find the previous distribution to the current one
  41. try:
  42. i = valid_distro_keys.index(distro_key)
  43. except ValueError:
  44. print('Distribution key (%s) not found in list of valid distributions.' % distro_key, file=sys.stderr)
  45. print('Valid rosdistros are %s.' % valid_distro_keys, file=sys.stderr)
  46. exit(-1)
  47. if i == 0 and not args.comparison:
  48. print('No previous distribution found.', file=sys.stderr)
  49. exit(-1)
  50. if args.comparison:
  51. valid_comparison_keys = valid_distro_keys[:]
  52. valid_comparison_keys.remove(distro_key)
  53. if args.comparison not in valid_comparison_keys:
  54. print('Invalid rosdistro [%s] selected for comparison to [%s].' % (args.comparison, distro_key),
  55. file=sys.stderr)
  56. print('Valid rosdistros are %s.' % valid_comparison_keys, file=sys.stderr)
  57. exit(-1)
  58. prev_distro_key = args.comparison
  59. else:
  60. prev_distro_key = valid_distro_keys[i - 1]
  61. cache = rosdistro.get_distribution_cache(index, distro_key)
  62. distro_file = cache.distribution_file
  63. prev_cache = rosdistro.get_distribution_cache(index, prev_distro_key)
  64. prev_distribution = rosdistro.get_cached_distribution(
  65. index, prev_distro_key, cache=prev_cache)
  66. prev_distro_file = prev_cache.distribution_file
  67. dependency_walker = DependencyWalker(prev_distribution)
  68. if repo_names_argument is None:
  69. # Check missing dependencies for packages that were in the previous
  70. # distribution that have not yet been released in the current distribution
  71. # Filter repos without a version or a release repository
  72. repo_names_argument = prev_distro_file.repositories.keys()
  73. prev_repo_names = set(
  74. repo for repo in repo_names_argument if is_released(repo, prev_distro_file))
  75. keys = distro_file.repositories.keys()
  76. current_repo_names = set(
  77. repo for repo in keys if is_released(repo, distro_file))
  78. # Print the repositories that will be eliminated from the input
  79. eliminated_repositories = prev_repo_names.intersection(
  80. current_repo_names)
  81. if len(eliminated_repositories) > 0:
  82. print('Ignoring inputs which have already been released:')
  83. print('\n'.join(
  84. sorted('\t{0}'.format(repo) for repo in eliminated_repositories)))
  85. repo_names_set = prev_repo_names.difference(
  86. current_repo_names)
  87. invalid_names = set(repo_names_argument).difference(prev_repo_names)
  88. if len(repo_names_set) == 0:
  89. print('All inputs are invalid or were already released in {0}.'.format(
  90. distro_key))
  91. if invalid_names:
  92. print('Could no resolve: %s in %s' % (list(invalid_names), prev_distro_key), file=sys.stderr)
  93. exit(1)
  94. print('Exiting without checking any dependencies.')
  95. exit(0)
  96. repo_names = list(repo_names_set)
  97. # Get a list of currently released packages
  98. current_package_names = set(
  99. pkg for repo in current_repo_names
  100. for pkg in distro_file.repositories[repo].release_repository.package_names)
  101. # Construct a dictionary where keys are repository names and values are a list
  102. # of the missing dependencies for that repository
  103. blocked_repos = {}
  104. unblocked_repos = set()
  105. total_blocking_repos = set()
  106. for repository_name in repo_names:
  107. repo = prev_distro_file.repositories[repository_name]
  108. release_repo = repo.release_repository
  109. package_dependencies = set()
  110. packages = release_repo.package_names
  111. # Accumulate all dependencies for those packages
  112. for package in packages:
  113. recursive_dependencies = dependency_walker.get_recursive_depends(
  114. package, ['build', 'run', 'buildtool'], ros_packages_only=True,
  115. limit_depth=args.depth)
  116. package_dependencies = package_dependencies.union(
  117. recursive_dependencies)
  118. # For all package dependencies, check if they are released yet
  119. unreleased_pkgs = package_dependencies.difference(
  120. current_package_names)
  121. # remove the packages which this repo provides.
  122. unreleased_pkgs = unreleased_pkgs.difference(packages)
  123. # Now get the repositories for these packages.
  124. blocking_repos = set(prev_distro_file.release_packages[pkg].repository_name
  125. for pkg in unreleased_pkgs)
  126. if len(blocking_repos) == 0:
  127. unblocked_repos.add(repository_name)
  128. else:
  129. # Get the repository for the unreleased packages
  130. blocked_repos[repository_name] = blocking_repos
  131. total_blocking_repos |= blocking_repos
  132. unblocked_blocking_repos = total_blocking_repos.intersection(unblocked_repos)
  133. unblocked_leaf_repos = unblocked_repos.difference(unblocked_blocking_repos)
  134. # Double-check repositories that we think are leaf repos
  135. for repo in unblocked_leaf_repos:
  136. # Check only one level of depends_on
  137. depends_on = dependency_walker.get_depends_on(package, 'build') | \
  138. dependency_walker.get_depends_on(package, 'run') | \
  139. dependency_walker.get_depends_on(package, 'buildtool')
  140. if len(depends_on) != 0:
  141. # There are packages that depend on this "leaf", but we didn't find
  142. # them initially because they weren't related to our inputs
  143. unblocked_blocking_repos.add(repo)
  144. unblocked_leaf_repos = unblocked_leaf_repos.difference(
  145. unblocked_blocking_repos)
  146. if len(blocked_repos.keys()) > 0:
  147. print('The following repos cannot be released because of unreleased '
  148. 'dependencies:')
  149. for blocked_repo_name in sorted(blocked_repos.keys()):
  150. unreleased_repos = blocked_repos[blocked_repo_name]
  151. print('\t{0}:'.format(blocked_repo_name))
  152. print('\n'.join(
  153. sorted('\t\t{0}'.format(repo) for repo in unreleased_repos)))
  154. if len(unblocked_leaf_repos) > 0:
  155. print('The following repos can be released, but do not block other repos:')
  156. print('\n'.join(
  157. sorted('\t{0}'.format(repo) for repo in unblocked_leaf_repos)))
  158. if len(unblocked_blocking_repos) > 0:
  159. print('The following repos can be released, and are blocking other repos:')
  160. print('\n'.join(
  161. sorted('\t{0}'.format(repo) for repo in unblocked_blocking_repos)))
  162. if len(invalid_names):
  163. print('Could no resolve the following arguments in %s: ' % prev_distro_key, file=sys.stderr)
  164. print('\n'.join(
  165. sorted('\t{0}'.format(repo) for repo in invalid_names)), file=sys.stderr)
  166. exit(1)