Codebase list ros-bloom / 4de751a
New upstream version 0.8.0 Jochen Sprickerhof 4 years ago
21 changed file(s) with 442 addition(s) and 274 deletion(s). Raw diff Collapse all Expand all
1212 - cd -
1313 - sudo `which rosdep` init
1414 - rosdep update
15 - git config --global user.email "test@example.com"
16 - git config --global user.name "Test User"
1715 # command to run tests
1816 script:
1917 - BLOOM_VERBOSE=1 python setup.py nosetests -s --tests test
0 0.8.0 (2019-04-12 13:45:00 -0800)
1 ---------------------------------
2 - Start release increment at 1. `#528 <https://github.com/ros-infrastructure/bloom/pull/528>`_
3 - Evaluate conditions in package.xml before resolving dependencies. `#519 <https://github.com/ros-infrastructure/bloom/pull/519>`_
4 - Update to prevent overwriting template files that exist in source. `#516 <https://github.com/ros-infrastructure/bloom/pull/516>`_
5 - Update debian templates to add trailing newline. `#523 <https://github.com/ros-infrastructure/bloom/pull/523>`_
6 - Fix str/bytes issue in Python 3 auth. `#522 <https://github.com/ros-infrastructure/bloom/pull/522>`_
7 - Use distribution type from index v4 to set ROS 2-specific behavior. `#502 <https://github.com/ros-infrastructure/bloom/pull/502>`_
8 - Fix tests to allow them to run outside of a git context. `#515 <https://github.com/ros-infrastructure/bloom/pull/515>`_
9 - Fix tests to allow empty git environment. `#514 <https://github.com/ros-infrastructure/bloom/pull/514>`_
10 - Invoke scripts using the current python executable. `#513 <https://github.com/ros-infrastructure/bloom/pull/513>`_
11 - Drop support for older distributions. (Retroactive to 0.6.8) `#512 <https://github.com/ros-infrastructure/bloom/pull/512>`_
12
013 0.7.2 (2019-01-26 07:45:00 -0800)
114 ---------------------------------
215 - Updated a test to support mixed rosdistro index. `#510 <https://github.com/ros-infrastructure/bloom/pull/510>`_
226226 settings['ros_distro'] = track_dict['ros_distro']
227227 # Release increment
228228 if 'last_version' in track_dict and track_dict['last_version'] != version:
229 next_release_inc = str(0)
229 next_release_inc = str(1)
230230 else:
231231 next_release_inc = str(int(track_dict['release_inc']) + 1)
232232 settings['release_inc'] = release_inc_override or next_release_inc
3535
3636 import argparse
3737 import atexit
38 import base64
3938 import datetime
4039 import difflib
41 import getpass
42 import json
4340 import os
4441 import pkg_resources
4542 import platform
7572 from bloom.git import inbranch
7673 from bloom.git import ls_tree
7774
78 from bloom.github import auth_header_from_basic_auth
79 from bloom.github import auth_header_from_oauth_token
80 from bloom.github import Github
8175 from bloom.github import GithubException
82 from bloom.github import GitHubAuthException
76 from bloom.github import get_gh_info
77 from bloom.github import get_github_interface
8378
8479 from bloom.logging import debug
8580 from bloom.logging import error
9287
9388 from bloom.packages import get_package_data
9489 from bloom.packages import get_ignored_packages
90
91 from bloom.rosdistro_api import get_distribution_file
92 from bloom.rosdistro_api import get_index
93 from bloom.rosdistro_api import get_most_recent
94 from bloom.rosdistro_api import get_rosdistro_index_commit
95 from bloom.rosdistro_api import get_rosdistro_index_original_branch
9596
9697 from bloom.summary import commit_summary
9798 from bloom.summary import get_summary_file
117118 import vcstools.__version__
118119 from vcstools.vcs_abstraction import get_vcs_client
119120
120 try:
121 import rosdistro
122 if parse_version(rosdistro.__version__) < parse_version('0.7.0'):
123 error("rosdistro version 0.7.0 or greater is required, found '{0}' from '{1}'."
124 .format(rosdistro.__version__, os.path.dirname(rosdistro.__file__)),
125 exit=True)
126 except ImportError:
127 debug(traceback.format_exc())
128 error("rosdistro was not detected, please install it.", file=sys.stderr,
129 exit=True)
121 from rosdistro import DistributionFile
122 from rosdistro import get_distribution_files
123 from rosdistro import get_index_url
130124 from rosdistro.writer import yaml_from_distribution_file
131125
132126 try:
161155 if os.path.exists(repo_path):
162156 shutil.rmtree(repo_path)
163157
164 _rosdistro_index = None
165 _rosdistro_distribution_files = {}
166 _rosdistro_index_commit = None
167 _rosdistro_index_original_branch = None
168
169
170 def get_index_url():
171 global _rosdistro_index_commit, _rosdistro_index_original_branch
172 index_url = rosdistro.get_index_url()
173 pr = urlparse(index_url)
174 if pr.netloc in ['raw.github.com', 'raw.githubusercontent.com']:
175 # Try to determine what the commit hash was
176 tokens = [x for x in pr.path.split('/') if x]
177 if len(tokens) <= 3:
178 debug("Failed to get commit for rosdistro index file: index url")
179 debug(tokens)
180 return index_url
181 owner = tokens[0]
182 repo = tokens[1]
183 branch = tokens[2]
184 gh = get_github_interface(quiet=True)
185 if gh is None:
186 # Failed to get it with auth, try without auth (may fail)
187 gh = Github(username=None, auth=None)
188 try:
189 data = gh.get_branch(owner, repo, branch)
190 except GithubException:
191 debug(traceback.format_exc())
192 debug("Failed to get commit for rosdistro index file: api")
193 return index_url
194 _rosdistro_index_commit = data.get('commit', {}).get('sha', None)
195 if _rosdistro_index_commit is not None:
196 info("ROS Distro index file associate with commit '{0}'"
197 .format(_rosdistro_index_commit))
198 # Also mutate the index_url to use the commit (rather than the moving branch name)
199 base_info = get_gh_info(index_url)
200 base_branch = base_info['branch']
201 rosdistro_index_commit = _rosdistro_index_commit # Copy global into local for substitution
202 middle = "{org}/{repo}".format(**base_info)
203 index_url = index_url.replace("{pr.netloc}/{middle}/{base_branch}/".format(**locals()),
204 "{pr.netloc}/{middle}/{rosdistro_index_commit}/".format(**locals()))
205 info("New ROS Distro index url: '{0}'".format(index_url))
206 _rosdistro_index_original_branch = base_branch
207 else:
208 debug("Failed to get commit for rosdistro index file: json")
209 return index_url
210
211
212 def get_index():
213 global _rosdistro_index
214 if _rosdistro_index is None:
215 _rosdistro_index = rosdistro.get_index(get_index_url())
216 if _rosdistro_index.version == 1:
217 error("This version of bloom does not support rosdistro version "
218 "'{0}', please use an older version of bloom."
219 .format(_rosdistro_index.version), exit=True)
220 if _rosdistro_index.version > 4:
221 error("This version of bloom does not support rosdistro version "
222 "'{0}', please update bloom.".format(_rosdistro_index.version), exit=True)
223 return _rosdistro_index
224
225
226 def list_distributions():
227 return sorted(get_index().distributions.keys())
228
229
230 def get_distribution_type(distro):
231 return get_index().distributions[distro].get('distribution_type')
232
233
234 def get_most_recent(thing_name, repository, reference_distro):
235 reference_distro_type = get_distribution_type(reference_distro)
236 distros_with_entry = {}
237 get_things = {
238 'release': lambda r: None if r.release_repository is None else r.release_repository,
239 'doc': lambda r: None if r.doc_repository is None else r.doc_repository,
240 'source': lambda r: None if r.source_repository is None else r.source_repository,
241 }
242 get_thing = get_things[thing_name]
243 for distro in list_distributions():
244 # skip distros with a different type if the information is available
245 if reference_distro_type is not None:
246 if get_distribution_type(distro) != reference_distro_type:
247 continue
248 distro_file = get_distribution_file(distro)
249 if repository in distro_file.repositories:
250 thing = get_thing(distro_file.repositories[repository])
251 if thing is not None:
252 distros_with_entry[distro] = thing
253 # Choose the alphabetical last distro which contained a release of this repository
254 default_distro = (sorted(distros_with_entry.keys()) or [None])[-1]
255 default_thing = distros_with_entry.get(default_distro, None)
256 return default_distro, default_thing
257
258
259 def get_distribution_file(distro):
260 global _rosdistro_distribution_files
261 if distro not in _rosdistro_distribution_files:
262 # REP 143, get list of distribution files and take the last one
263 files = rosdistro.get_distribution_files(get_index(), distro)
264 if not files:
265 error("No distribution files listed for distribution '{0}'."
266 .format(distro), exit=True)
267 _rosdistro_distribution_files[distro] = files[-1]
268 return _rosdistro_distribution_files[distro]
269158
270159 _rosdistro_distribution_file_urls = {}
271160
425314
426315 def get_relative_distribution_file_path(distro):
427316 distribution_file_url = urlparse(get_distribution_file_url(distro))
428 index_file_url = urlparse(rosdistro.get_index_url())
317 index_file_url = urlparse(get_index_url())
429318 return os.path.relpath(distribution_file_url.path,
430319 os.path.commonprefix([index_file_url.path, distribution_file_url.path]))
431320
626515
627516 # Do the diff
628517 distro_file_name = get_relative_distribution_file_path(distro)
629 updated_distribution_file = rosdistro.DistributionFile(distro, distribution_dict)
518 updated_distribution_file = DistributionFile(distro, distribution_dict)
630519 distro_dump = yaml_from_distribution_file(updated_distribution_file)
631520 distro_file_raw = load_url_to_file_handle(get_distribution_file_url(distro)).read().decode('utf-8')
632521 if distro_file_raw != distro_dump:
678567 return None
679568
680569
681 def get_gh_info(url):
682 o = urlparse(url)
683 if 'raw.github.com' not in o.netloc and 'raw.githubusercontent.com' not in o.netloc:
684 return None
685 url_paths = o.path.split('/')
686 if len(url_paths) < 5:
687 return None
688 return {'server': 'github.com',
689 'org': url_paths[1],
690 'repo': url_paths[2],
691 'branch': url_paths[3],
692 'path': '/'.join(url_paths[4:])}
693
694
695570 def get_repo_info(distro_url):
696571 gh_info = get_gh_info(distro_url)
697572 if gh_info:
698573 return gh_info
699
700
701 _gh = None
702
703
704 def get_github_interface(quiet=False):
705 def mfa_prompt(oauth_config_path, username):
706 """Explain how to create a token for users with Multi-Factor Authentication configured."""
707 warning("Receiving 401 when trying to create an oauth token can be caused by the user "
708 "having two-factor authentication enabled.")
709 warning("If 2FA is enabled, the user will have to create an oauth token manually.")
710 warning("A token can be created at https://github.com/settings/tokens")
711 warning("The resulting token can be placed in the '{oauth_config_path}' file as such:"
712 .format(**locals()))
713 info("")
714 warning('{{"github_user": "{username}", "oauth_token": "TOKEN_GOES_HERE"}}'
715 .format(**locals()))
716 info("")
717
718 global _gh
719 if _gh is not None:
720 return _gh
721 # First check to see if the oauth token is stored
722 oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
723 config = {}
724 if os.path.exists(oauth_config_path):
725 with open(oauth_config_path, 'r') as f:
726 config = json.loads(f.read())
727 token = config.get('oauth_token', None)
728 username = config.get('github_user', None)
729 if token and username:
730 return Github(username, auth=auth_header_from_oauth_token(token), token=token)
731 if not os.path.isdir(os.path.dirname(oauth_config_path)):
732 os.makedirs(os.path.dirname(oauth_config_path))
733 if quiet:
734 return None
735 # Ok, now we have to ask for the user name and pass word
736 info("")
737 warning("Looks like bloom doesn't have an oauth token for you yet.")
738 warning("Therefore bloom will require your GitHub username and password just this once.")
739 warning("With your GitHub username and password bloom will create an oauth token on your behalf.")
740 warning("The token will be stored in `~/.config/bloom`.")
741 warning("You can delete the token from that file to have a new token generated.")
742 warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
743 warning("If you need to unauthorize it, remove it from the 'Applications' menu in your GitHub account page.")
744 info("")
745 if not maybe_continue('y', "Would you like to create an OAuth token now"):
746 return None
747 token = None
748 while token is None:
749 try:
750 username = getpass.getuser()
751 username = safe_input("GitHub username [{0}]: ".format(username)) or username
752 password = getpass.getpass("GitHub password (never stored): ")
753 except (KeyboardInterrupt, EOFError):
754 return None
755 if not password:
756 error("No password was given, aborting.")
757 return None
758 gh = Github(username, auth=auth_header_from_basic_auth(username, password))
759 try:
760 token = gh.create_new_bloom_authorization(update_auth=True)
761 with open(oauth_config_path, 'w') as f:
762 config.update({'oauth_token': token, 'github_user': username})
763 f.write(json.dumps(config))
764 info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
765 .format(**locals()))
766 except GitHubAuthException as exc:
767 error("{0}".format(exc))
768 mfa_prompt(oauth_config_path, username)
769 except GithubException as exc:
770 error("{0}".format(exc))
771 info("")
772 if hasattr(exc, 'resp') and '{0}'.format(exc.resp.status) in ['401']:
773 mfa_prompt(oauth_config_path, username)
774 warning("This sometimes fails when the username or password are incorrect, try again?")
775 if not maybe_continue():
776 return None
777 _gh = gh
778 return gh
779574
780575
781576 def get_changelog_summary(release_tag):
813608
814609
815610 def open_pull_request(track, repository, distro, interactive, override_release_repository_url):
816 global _rosdistro_index_commit
817611 # Get the diff
818612 distribution_file = get_distribution_file(distro)
819613 if repository in distribution_file.repositories and \
836630 return
837631
838632 # If we did replace the branch in the url with a commit, restore that now
839 if _rosdistro_index_original_branch is not None:
840 base_info['branch'] = _rosdistro_index_original_branch
633 rosdistro_index_original_branch = get_rosdistro_index_original_branch()
634 if rosdistro_index_original_branch is not None:
635 base_info['branch'] = rosdistro_index_original_branch
841636
842637 # Create content for PR
843638 title = "{0}: {1} in '{2}' [bloom]".format(repository, version, base_info['path'])
946741 _my_run('git checkout -b {new_branch}'.format(**locals()))
947742 _my_run("git pull {rosdistro_url} {base_info[branch]}".format(**locals()),
948743 "Pulling latest rosdistro branch")
949 if _rosdistro_index_commit is not None:
950 _my_run('git reset --hard {_rosdistro_index_commit}'.format(**globals()))
744 rosdistro_index_commit = get_rosdistro_index_commit()
745 if rosdistro_index_commit is not None:
746 _my_run('git reset --hard {rosdistro_index_commit}'.format(**locals()))
951747 with open('{0}'.format(base_info['path']), 'w') as f:
952748 info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(base_info['path']))
953749 f.write(updated_distro_file_yaml)
189189 'patches': PromptEntry('Patches Directory', spec=config_spec['patches']),
190190 'ros_distro': PromptEntry('ROS Distro', default='indigo', spec=config_spec['ros_distro']),
191191 'release_repo_url': PromptEntry('Release Repository Push URL', spec=config_spec['release_repo_url']),
192 'release_inc': -1,
192 'release_inc': 0,
193193 'actions': [
194194 'bloom-export-upstream :{vcs_local_uri} :{vcs_type}'
195195 ' --tag :{release_tag} --display-uri :{vcs_uri}'
3939 from bloom.logging import error
4040 from bloom.logging import info
4141
42 from bloom.rosdistro_api import get_distribution_type
43
4244 from bloom.util import code
4345 from bloom.util import maybe_continue
4446 from bloom.util import print_exc
114116 default_os_installer)
115117 assert inst_key in os_installers
116118 return installer.resolve(rule), inst_key, default_os_installer
119
120
121 def package_conditional_context(ros_distro):
122 distribution_type = get_distribution_type(ros_distro)
123 if distribution_type == 'ros1':
124 ros_version = '1'
125 elif distribution_type == 'ros2':
126 ros_version = '2'
127 else:
128 error("Bloom cannot cope with distribution_type '{0}'".format(
129 distribution_type), exit=True)
130 return {
131 'ROS_VERSION': ros_version,
132 'ROS_DISTRO': ros_distro,
133 }
117134
118135
119136 def resolve_rosdep_key(
5757
5858 from bloom.generators.common import default_fallback_resolver
5959 from bloom.generators.common import invalidate_view_cache
60 from bloom.generators.common import package_conditional_context
6061 from bloom.generators.common import resolve_rosdep_key
6162
6263 from bloom.git import inbranch
146147 if not os.path.exists(dst):
147148 os.makedirs(dst)
148149 if os.path.exists(template_dst):
149 debug("Removing existing file '{0}'".format(template_dst))
150 os.remove(template_dst)
151 with io.open(template_dst, 'w', encoding='utf-8') as f:
152 if not isinstance(template, str):
153 template = template.decode('utf-8')
154 # Python 2 API needs a `unicode` not a utf-8 string.
155 elif sys.version_info.major == 2:
156 template = template.decode('utf-8')
157 f.write(template)
158 shutil.copystat(template_abs_path, template_dst)
150 debug("Not overwriting existing file '{0}'".format(template_dst))
151 else:
152 with io.open(template_dst, 'w', encoding='utf-8') as f:
153 if not isinstance(template, str):
154 template = template.decode('utf-8')
155 # Python 2 API needs a `unicode` not a utf-8 string.
156 elif sys.version_info.major == 2:
157 template = template.decode('utf-8')
158 f.write(template)
159 shutil.copystat(template_abs_path, template_dst)
159160
160161
161162 def place_template_files(path, build_type, gbp=False):
311312 # Installation prefix
312313 data['InstallationPrefix'] = installation_prefix
313314 # Resolve dependencies
314 depends = package.run_depends + package.buildtool_export_depends
315 build_depends = package.build_depends + package.buildtool_depends + package.test_depends
316
317 unresolved_keys = depends + build_depends + package.replaces + package.conflicts
315 package.evaluate_conditions(package_conditional_context(ros_distro))
316 depends = [
317 dep for dep in (package.run_depends + package.buildtool_export_depends)
318 if dep.evaluated_condition]
319 build_depends = [
320 dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends)
321 if dep.evaluated_condition]
322
323 unresolved_keys = [
324 dep for dep in (depends + build_depends + package.replaces + package.conflicts)
325 if dep.evaluated_condition]
318326 # The installer key is not considered here, but it is checked when the keys are checked before this
319327 resolved_deps = resolve_dependencies(unresolved_keys, os_name,
320328 os_version, ros_distro,
646654 update_rosdep()
647655 self.has_run_rosdep = True
648656
649 def _check_all_keys_are_valid(self, peer_packages):
657 def _check_all_keys_are_valid(self, peer_packages, ros_distro):
650658 keys_to_resolve = []
651659 key_to_packages_which_depends_on = collections.defaultdict(list)
652660 keys_to_ignore = set()
653661 for package in self.packages.values():
654 depends = package.run_depends + package.buildtool_export_depends
655 build_depends = package.build_depends + package.buildtool_depends + package.test_depends
656 unresolved_keys = depends + build_depends + package.replaces + package.conflicts
657 keys_to_ignore = keys_to_ignore.union(package.replaces + package.conflicts)
662 package.evaluate_conditions(package_conditional_context(ros_distro))
663 depends = [
664 dep for dep in (package.run_depends + package.buildtool_export_depends)
665 if dep.evaluated_condition]
666 build_depends = [
667 dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends)
668 if dep.evaluated_condition]
669 unresolved_keys = [
670 dep for dep in (depends + build_depends + package.replaces + package.conflicts)
671 if dep.evaluated_condition]
672 keys_to_ignore = {
673 dep for dep in keys_to_ignore.union(package.replaces + package.conflicts)
674 if dep.evaluated_condition}
658675 keys = [d.name for d in unresolved_keys]
659676 keys_to_resolve.extend(keys)
660677 for key in keys:
698715
699716 peer_packages = [p.name for p in self.packages.values()]
700717
701 while not self._check_all_keys_are_valid(peer_packages):
718 while not self._check_all_keys_are_valid(peer_packages, self.rosdistro):
702719 error("Some of the dependencies for packages in this repository could not be resolved by rosdep.")
703720 error("You can try to address the issues which appear above and try again if you wish.")
704721 try:
837854 place_template_files('.', build_type, gbp=True)
838855 # Commit results
839856 execute_command('git add ' + debian_dir)
840 execute_command('git commit -m "Placing debian template files"')
857 _, has_files, _ = execute_command('git diff --cached --name-only', return_io=True)
858 if has_files:
859 execute_command('git commit -m "Placing debian template files"')
841860
842861 def get_releaser_history(self):
843862 # Assumes that this is called in the target branch
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
0 @(debhelper_version)
4343
4444 from bloom.logging import info
4545
46 from bloom.rosdistro_api import get_index
47
4648 from bloom.util import get_distro_list_prompt
4749
4850
8789 subs['Package'] = rosify_package_name(subs['Package'], self.rosdistro)
8890
8991 # ROS 2 specific bloom extensions.
90 # TODO(nuclearsandwich) explore other ways to enable these extensions, reduce their necessity,
91 # or make them configurable rather than relying on distro names.
92 if self.rosdistro in ['r2b2', 'r2b3', 'ardent', 'bouncy', 'crystal']:
92 ros2_distros = [
93 name for name, values in get_index().distributions.items()
94 if values.get('distribution_type') == 'ros2']
95 if self.rosdistro in ros2_distros:
9396 # Add ros-workspace package as a dependency to any package other
9497 # than ros_workspace and its dependencies.
9598 if package.name not in ['ament_cmake_core', 'ament_package', 'ros_workspace']:
99102
100103 # Add packages necessary to build vendor typesupport for rosidl_interface_packages to their
101104 # build dependencies.
102 if self.rosdistro in ['bouncy', 'crystal'] and \
105 if self.rosdistro in ros2_distros and \
106 self.rosdistro not in ('r2b2', 'r2b3', 'ardent') and \
103107 'rosidl_interface_packages' in [p.name for p in package.member_of_groups]:
104108 ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES = [
105109 'rmw-connext-cpp',
5959
6060 from bloom.generators.common import default_fallback_resolver
6161 from bloom.generators.common import invalidate_view_cache
62 from bloom.generators.common import package_conditional_context
6263 from bloom.generators.common import resolve_rosdep_key
6364
6465 from bloom.git import inbranch
230231 # Installation prefix
231232 data['InstallationPrefix'] = installation_prefix
232233 # Resolve dependencies
233 depends = package.run_depends + package.buildtool_export_depends
234 build_depends = package.build_depends + package.buildtool_depends + package.test_depends
235 unresolved_keys = depends + build_depends + package.replaces + package.conflicts
234 package.evaluate_conditions(package_conditional_context(ros_distro))
235 depends = [
236 dep for dep in (package.run_depends + package.buildtool_export_depends)
237 if dep.evaluated_condition]
238 build_depends = [
239 dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends)
240 if dep.evaluated_condition]
241 unresolved_keys = [
242 dep for dep in (depends + build_depends + package.replaces + package.conflicts)
243 if dep.evaluated_condition]
236244 # The installer key is not considered here, but it is checked when the keys are checked before this
237245 resolved_deps = resolve_dependencies(unresolved_keys, os_name,
238246 os_version, ros_distro,
525533 update_rosdep()
526534 self.has_run_rosdep = True
527535
528 def _check_all_keys_are_valid(self, peer_packages):
536 def _check_all_keys_are_valid(self, peer_packages, rosdistro):
529537 keys_to_resolve = []
530538 key_to_packages_which_depends_on = collections.defaultdict(list)
531539 keys_to_ignore = set()
532540 for package in self.packages.values():
533 depends = package.run_depends + package.buildtool_export_depends
534 build_depends = package.build_depends + package.buildtool_depends + package.test_depends
535 unresolved_keys = depends + build_depends + package.replaces + package.conflicts
536 keys_to_ignore = keys_to_ignore.union(package.replaces + package.conflicts)
541 package.evaluate_conditions(package_conditional_context(rosdistro))
542 depends = [
543 dep for dep in (package.run_depends + package.buildtool_export_depends)
544 if dep.evaluated_condition]
545 build_depends = [
546 dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends)
547 if dep.evaluated_condition]
548 unresolved_keys = [
549 dep for dep in (depends + build_depends + package.replaces + package.conflicts)
550 if dep.evaluated_condition]
551 keys_to_ignore = {
552 dep for dep in keys_to_ignore.union(package.replaces + package.conflicts)
553 if dep.evaluated_condition}
537554 keys = [d.name for d in unresolved_keys]
538555 keys_to_resolve.extend(keys)
539556 for key in keys:
577594
578595 peer_packages = [p.name for p in self.packages.values()]
579596
580 while not self._check_all_keys_are_valid(peer_packages):
597 while not self._check_all_keys_are_valid(peer_packages, self.rosdistro):
581598 error("Some of the dependencies for packages in this repository could not be resolved by rosdep.")
582599 error("You can try to address the issues which appear above and try again if you wish, "
583600 "or continue without releasing into RPM-based distributions (e.g. Fedora 24).")
3838
3939 import base64
4040 import datetime
41 import getpass
4142 import json
43 import os
4244 import socket
4345 import sys
46
47 from bloom.logging import error
48 from bloom.logging import info
49 from bloom.logging import warning
50
51 from bloom.util import maybe_continue
52 from bloom.util import safe_input
4453
4554
4655 try:
4958 from urllib2 import HTTPError
5059 from urllib2 import Request, urlopen
5160 from urllib2 import URLError
61 from urlparse import urlparse
5262 from urlparse import urlunsplit
5363 except ImportError:
5464 # Python3
5565 from urllib.error import HTTPError
5666 from urllib.error import URLError
67 from urllib.parse import urlparse
5768 from urllib.parse import urlunsplit
5869 from urllib.request import Request, urlopen
5970
6172
6273
6374 def auth_header_from_basic_auth(user, password):
64 return "Basic {0}".format(base64.b64encode('{0}:{1}'.format(user, password)))
75 auth_str = '{0}:{1}'.format(user, password)
76 if sys.version_info >= (3, 0):
77 auth_str = auth_str.encode()
78 return "Basic {0}".format(base64.b64encode(auth_str))
6579
6680
6781 def auth_header_from_oauth_token(token):
239253 raise GithubException("Failed to create pull request", resp)
240254 resp_json = json_loads(resp)
241255 return resp_json['html_url']
256
257
258 def get_gh_info(url):
259 o = urlparse(url)
260 if 'raw.github.com' not in o.netloc and 'raw.githubusercontent.com' not in o.netloc:
261 return None
262 url_paths = o.path.split('/')
263 if len(url_paths) < 5:
264 return None
265 return {'server': 'github.com',
266 'org': url_paths[1],
267 'repo': url_paths[2],
268 'branch': url_paths[3],
269 'path': '/'.join(url_paths[4:])}
270
271
272 _gh = None
273
274
275 def get_github_interface(quiet=False):
276 def mfa_prompt(oauth_config_path, username):
277 """Explain how to create a token for users with Multi-Factor Authentication configured."""
278 warning("Receiving 401 when trying to create an oauth token can be caused by the user "
279 "having two-factor authentication enabled.")
280 warning("If 2FA is enabled, the user will have to create an oauth token manually.")
281 warning("A token can be created at https://github.com/settings/tokens")
282 warning("The resulting token can be placed in the '{oauth_config_path}' file as such:"
283 .format(**locals()))
284 info("")
285 warning('{{"github_user": "{username}", "oauth_token": "TOKEN_GOES_HERE"}}'
286 .format(**locals()))
287 info("")
288
289 global _gh
290 if _gh is not None:
291 return _gh
292 # First check to see if the oauth token is stored
293 oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
294 config = {}
295 if os.path.exists(oauth_config_path):
296 with open(oauth_config_path, 'r') as f:
297 config = json.loads(f.read())
298 token = config.get('oauth_token', None)
299 username = config.get('github_user', None)
300 if token and username:
301 return Github(username, auth=auth_header_from_oauth_token(token), token=token)
302 if not os.path.isdir(os.path.dirname(oauth_config_path)):
303 os.makedirs(os.path.dirname(oauth_config_path))
304 if quiet:
305 return None
306 # Ok, now we have to ask for the user name and pass word
307 info("")
308 warning("Looks like bloom doesn't have an oauth token for you yet.")
309 warning("Therefore bloom will require your GitHub username and password just this once.")
310 warning("With your GitHub username and password bloom will create an oauth token on your behalf.")
311 warning("The token will be stored in `~/.config/bloom`.")
312 warning("You can delete the token from that file to have a new token generated.")
313 warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
314 warning("If you need to unauthorize it, remove it from the 'Applications' menu in your GitHub account page.")
315 info("")
316 if not maybe_continue('y', "Would you like to create an OAuth token now"):
317 return None
318 token = None
319 while token is None:
320 try:
321 username = getpass.getuser()
322 username = safe_input("GitHub username [{0}]: ".format(username)) or username
323 password = getpass.getpass("GitHub password (never stored): ")
324 except (KeyboardInterrupt, EOFError):
325 return None
326 if not password:
327 error("No password was given, aborting.")
328 return None
329 gh = Github(username, auth=auth_header_from_basic_auth(username, password))
330 try:
331 token = gh.create_new_bloom_authorization(update_auth=True)
332 with open(oauth_config_path, 'w') as f:
333 config.update({'oauth_token': token, 'github_user': username})
334 f.write(json.dumps(config))
335 info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
336 .format(**locals()))
337 except GitHubAuthException as exc:
338 error("{0}".format(exc))
339 mfa_prompt(oauth_config_path, username)
340 except GithubException as exc:
341 error("{0}".format(exc))
342 info("")
343 if hasattr(exc, 'resp') and '{0}'.format(exc.resp.status) in ['401']:
344 mfa_prompt(oauth_config_path, username)
345 warning("This sometimes fails when the username or password are incorrect, try again?")
346 if not maybe_continue():
347 return None
348 _gh = gh
349 return gh
0 # Software License Agreement (BSD License)
1 #
2 # Copyright (c) 2014, Open Source Robotics Foundation, Inc.
3 # Copyright (c) 2013, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32
33 from __future__ import print_function
34 from __future__ import unicode_literals
35
36 import os
37 import sys
38 import traceback
39
40 from pkg_resources import parse_version
41
42 # python2/3 compatibility
43 try:
44 from urllib.parse import urlparse
45 except ImportError:
46 from urlparse import urlparse
47
48 from bloom.github import Github
49 from bloom.github import GithubException
50 from bloom.github import get_gh_info
51 from bloom.github import get_github_interface
52
53 from bloom.logging import debug
54 from bloom.logging import error
55 from bloom.logging import info
56
57
58 try:
59 import rosdistro
60 if parse_version(rosdistro.__version__) < parse_version('0.7.0'):
61 error("rosdistro version 0.7.0 or greater is required, found '{0}' from '{1}'."
62 .format(rosdistro.__version__, os.path.dirname(rosdistro.__file__)),
63 exit=True)
64 except ImportError:
65 debug(traceback.format_exc())
66 error("rosdistro was not detected, please install it.", file=sys.stderr,
67 exit=True)
68
69 _rosdistro_index = None
70 _rosdistro_distribution_files = {}
71 _rosdistro_index_commit = None
72 _rosdistro_index_original_branch = None
73
74
75 def get_index_url():
76 global _rosdistro_index_commit, _rosdistro_index_original_branch
77 index_url = rosdistro.get_index_url()
78 pr = urlparse(index_url)
79 if pr.netloc in ['raw.github.com', 'raw.githubusercontent.com']:
80 # Try to determine what the commit hash was
81 tokens = [x for x in pr.path.split('/') if x]
82 if len(tokens) <= 3:
83 debug("Failed to get commit for rosdistro index file: index url")
84 debug(tokens)
85 return index_url
86 owner = tokens[0]
87 repo = tokens[1]
88 branch = tokens[2]
89 gh = get_github_interface(quiet=True)
90 if gh is None:
91 # Failed to get it with auth, try without auth (may fail)
92 gh = Github(username=None, auth=None)
93 try:
94 data = gh.get_branch(owner, repo, branch)
95 except GithubException:
96 debug(traceback.format_exc())
97 debug("Failed to get commit for rosdistro index file: api")
98 return index_url
99 _rosdistro_index_commit = data.get('commit', {}).get('sha', None)
100 if _rosdistro_index_commit is not None:
101 info("ROS Distro index file associate with commit '{0}'"
102 .format(_rosdistro_index_commit))
103 # Also mutate the index_url to use the commit (rather than the moving branch name)
104 base_info = get_gh_info(index_url)
105 base_branch = base_info['branch']
106 rosdistro_index_commit = _rosdistro_index_commit # Copy global into local for substitution
107 middle = "{org}/{repo}".format(**base_info)
108 index_url = index_url.replace("{pr.netloc}/{middle}/{base_branch}/".format(**locals()),
109 "{pr.netloc}/{middle}/{rosdistro_index_commit}/".format(**locals()))
110 info("New ROS Distro index url: '{0}'".format(index_url))
111 _rosdistro_index_original_branch = base_branch
112 else:
113 debug("Failed to get commit for rosdistro index file: json")
114 return index_url
115
116
117 def get_index():
118 global _rosdistro_index
119 if _rosdistro_index is None:
120 _rosdistro_index = rosdistro.get_index(get_index_url())
121 if _rosdistro_index.version == 1:
122 error("This version of bloom does not support rosdistro version "
123 "'{0}', please use an older version of bloom."
124 .format(_rosdistro_index.version), exit=True)
125 if _rosdistro_index.version > 4:
126 error("This version of bloom does not support rosdistro version "
127 "'{0}', please update bloom.".format(_rosdistro_index.version), exit=True)
128 return _rosdistro_index
129
130
131 def list_distributions():
132 return sorted(get_index().distributions.keys())
133
134
135 def get_distribution_type(distro):
136 return get_index().distributions[distro].get('distribution_type')
137
138
139 def get_most_recent(thing_name, repository, reference_distro):
140 reference_distro_type = get_distribution_type(reference_distro)
141 distros_with_entry = {}
142 get_things = {
143 'release': lambda r: None if r.release_repository is None else r.release_repository,
144 'doc': lambda r: None if r.doc_repository is None else r.doc_repository,
145 'source': lambda r: None if r.source_repository is None else r.source_repository,
146 }
147 get_thing = get_things[thing_name]
148 for distro in list_distributions():
149 # skip distros with a different type if the information is available
150 if reference_distro_type is not None:
151 if get_distribution_type(distro) != reference_distro_type:
152 continue
153 distro_file = get_distribution_file(distro)
154 if repository in distro_file.repositories:
155 thing = get_thing(distro_file.repositories[repository])
156 if thing is not None:
157 distros_with_entry[distro] = thing
158 # Choose the alphabetical last distro which contained a release of this repository
159 default_distro = (sorted(distros_with_entry.keys()) or [None])[-1]
160 default_thing = distros_with_entry.get(default_distro, None)
161 return default_distro, default_thing
162
163
164 def get_distribution_file(distro):
165 global _rosdistro_distribution_files
166 if distro not in _rosdistro_distribution_files:
167 # REP 143, get list of distribution files and take the last one
168 files = rosdistro.get_distribution_files(get_index(), distro)
169 if not files:
170 error("No distribution files listed for distribution '{0}'."
171 .format(distro), exit=True)
172 _rosdistro_distribution_files[distro] = files[-1]
173 return _rosdistro_distribution_files[distro]
174
175
176 def get_rosdistro_index_commit():
177 return _rosdistro_index_commit
178
179
180 def get_rosdistro_index_original_branch():
181 return _rosdistro_index_original_branch
1313 from __future__ import print_function
1414
1515 import os
16 import sys
1617
1718 # If extensions (or modules to document with autodoc) are in another directory,
1819 # add these directories to sys.path here. If the directory is relative to the
6061 # The full version, including alpha/beta/rc tags.
6162 setup_py = os.path.join(os.path.dirname(__file__), '..', 'setup.py')
6263 import subprocess
63 release = subprocess.check_output('/usr/bin/env python ' + setup_py + ' --version', shell=True).strip()
64 release = subprocess.check_output(sys.executable + ' ' + setup_py + ' --version', shell=True).strip().decode('utf-8')
6465 print('Using release version: {0}'.format(release))
6566
6667 # The language for content autogenerated by Sphinx. Refer to documentation
2121
2222 setup(
2323 name='bloom',
24 version='0.7.2',
24 version='0.8.0',
2525 packages=find_packages(exclude=['test', 'test.*']),
2626 package_data={
2727 'bloom.generators.debian': [
33 Conflicts: python3-bloom
44 Conflicts3: python-bloom
55 Copyright-File: LICENSE.txt
6 Suite: oneiric precise quantal raring saucy trusty utopic vivid wily xenial yakkety zesty artful bionic wheezy jessie stretch buster
7 X-Python3-Version: >= 3.2
6 Suite: trusty utopic vivid wily xenial yakkety zesty artful bionic jessie stretch buster
7 X-Python3-Version: >= 3.4
44 scripts = os.path.join(os.path.dirname(__file__), '..', 'scripts')
55 scripts = os.path.abspath(scripts)
66 os.environ['PATH'] = scripts + ':' + os.environ['PATH']
7
8 user_email = 'test@example.com'
9 user_name = 'Test User'
10
11 os.environ.setdefault('GIT_AUTHOR_NAME', user_name)
12 os.environ.setdefault('GIT_AUTHOR_EMAIL', user_email)
13 os.environ.setdefault('GIT_COMMITTER_NAME', user_name)
14 os.environ.setdefault('GIT_COMMITTER_EMAIL', user_email)
131131 "no patches/release/melodic/foo branch"
132132 # was the release tag created?
133133 ret, out, err = user('git tag', return_io=True)
134 expected = 'release/melodic/foo/' + version + '-0'
134 expected = 'release/melodic/foo/' + version + '-1'
135135 assert out.count(expected) == 1, \
136136 "no release tag created, expected: '{0}'".format(expected)
137137
265265 assert branch_exists('patches/release/melodic/' + pkg), \
266266 "no patches/release/melodic/" + pkg + " branch"
267267 # Did the release tag get created?
268 assert out.count('release/melodic/' + pkg + '/0.1.0-0') == 1, \
268 assert out.count('release/melodic/' + pkg + '/0.1.0-1') == 1, \
269269 "no release tag created for " + pkg
270270 # Is there a package.xml in the top level?
271271 with inbranch('release/melodic/' + pkg):
298298 assert branch_exists('patches/release/melodic/' + pkg), \
299299 "no patches/release/melodic/" + pkg + " branch"
300300 # Did the release tag get created?
301 assert out.count('release/melodic/' + pkg + '/0.1.0-0') == 1, \
301 assert out.count('release/melodic/' + pkg + '/0.1.0-1') == 1, \
302302 "no release tag created for " + pkg
303303 # Is there a package.xml in the top level?
304304 with inbranch('release/melodic/' + pkg):
328328 assert branch_exists(patches_branch), \
329329 "no " + patches_branch + " branch"
330330 # Did the debian tag get created?
331 tag = 'debian/ros-melodic-' + pkg_san + '_0.1.0-0_' + distro
331 tag = 'debian/ros-melodic-' + pkg_san + '_0.1.0-1_' + distro
332332 assert out.count(tag) == 1, \
333333 "no '" + tag + "'' tag created for '" + pkg + "': `\n" + \
334334 out + "\n`"
00 import os
11
22 from ..utils.common import AssertRaisesContext
3 from ..utils.common import in_temporary_directory
34 from ..utils.common import redirected_stdio
5 from ..utils.common import user
46
57 from bloom.packages import get_package_data
68
79 test_data_dir = os.path.join(os.path.dirname(__file__), 'test_packages_data')
810
911
12 @in_temporary_directory
1013 def test_get_package_data_fails_on_uppercase():
14 user('git init .')
15
1116 with AssertRaisesContext(SystemExit, "Invalid package names, aborting."):
1217 with redirected_stdio():
1318 get_package_data(directory=test_data_dir)