pax_global_header00006660000000000000000000000064140364172010014510gustar00rootroot0000000000000052 comment=27ed9a0a306116d856e5f4d208a31bacbb83774b bloom-0.10.7/000077500000000000000000000000001403641720100127055ustar00rootroot00000000000000bloom-0.10.7/.github/000077500000000000000000000000001403641720100142455ustar00rootroot00000000000000bloom-0.10.7/.github/workflows/000077500000000000000000000000001403641720100163025ustar00rootroot00000000000000bloom-0.10.7/.github/workflows/ci.yaml000066400000000000000000000026741403641720100175720ustar00rootroot00000000000000name: bloom-ci on: push: branches: ['master'] pull_request: branches: ['*'] jobs: build: strategy: matrix: os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04, macos-latest] python: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] exclude: - os: ubuntu-16.04 python: 3.6 - os: ubuntu-16.04 python: 3.7 - os: ubuntu-16.04 python: 3.8 - os: ubuntu-16.04 python: 3.9 - os: ubuntu-18.04 python: 3.5 - os: ubuntu-20.04 python: 2.7 - os: ubuntu-20.04 python: 3.5 - os: ubuntu-20.04 python: 3.6 - os: ubuntu-20.04 python: 3.7 - os: macos-latest python: 3.5 - os: macos-latest python: 3.6 name: bloom tests runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v2 - name: Set up Python ${{matrix.python}} uses: actions/setup-python@v1 with: python-version: ${{matrix.python}} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools python -m pip install PyYAML argparse empy rosdep vcstools catkin-pkg python-dateutil python -m pip install nose coverage pep8 - name: Run tests run: | BLOOM_VERBOSE=1 python setup.py nosetests -s --tests test bloom-0.10.7/.gitignore000077500000000000000000000003311403641720100146750ustar00rootroot00000000000000*.py[co] *~ # Packages *.egg *.egg-info dist build eggs parts var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox MANIFEST *.DS_Store deb_dist .fuse_* bloom-0.10.7/.travis.yml000066400000000000000000000011011403641720100150070ustar00rootroot00000000000000language: python python: - "3.4" # When support for 3.4 is removed unpin the PyYAML version below. # command to install dependencies install: - if [ $TRAVIS_PYTHON_VERSION == "3.4" ]; then pip install PyYAML==5.2; fi # Forcing PyYAML 5.2 while we retain Python 3.4 support PyYAML 5.3 and higher does not support python 3.4 - pip install PyYAML argparse empy rosdep vcstools catkin-pkg python-dateutil setuptools - pip install nose coverage pep8 # command to run tests script: - BLOOM_VERBOSE=1 python setup.py nosetests -s --tests test notifications: email: false bloom-0.10.7/CHANGELOG.rst000066400000000000000000001051711403641720100147330ustar00rootroot000000000000000.10.7 (2021-04-16 11:30:00 -0700) ---------------------------------- - Fix basic authentication issue affecting new GitHub tokens. `#633 `_ 0.10.6 (2021-04-07 11:30:00 -0700) --------------------------------- - Fix a packaging bug affecting only Debian packages. 0.10.5 (2021-04-07 10:30:00 -0700) --------------------------------- - Fix a packaging bug affecting only Debian packages. 0.10.4 (2021-04-07 10:00:00 -0700) ---------------------------------- - Use basic authentication with the new personal access tokens. `#627 `_ - Add a fast check for the likely fork name. `#629 `_ - Specify patch level in RPM templates. `#626 `_ - Collect manually created token rather than attempting to create one. `#628 `_ 0.10.3 (2021-03-25 11:08:00 -0700) ---------------------------------- - Rewire the typesupport dependencies for post-Foxy. `#625 `_ 0.10.2 (2021-03-11 17:05:00 -0800) ---------------------------------- - Drop Connext from typesupport dependencies for RPMs `#623 `_ - Enable output from CTest failures in RPMs `#620 `_ - Only suggest resetting action list if it's old `#622 `_ 0.10.1 (2021-02-04 15:00:00 -0800) ---------------------------------- - Do not verify package version on ignored packages `#610 `_ - Update email addresses and remove 404 download url `#607 `_ - Align CMake args in RPMs with debs `#617 `_ - Add a template substitution for the ROS distro `#612 `_ - Create and use a 'fake' rosdep cache for tests `#614 `_ - Add --debian-inc option to bloom-generate `#428 `_ - Solve shlibdeps errors in REP136 packages that use GNUInstallDirs. `#600 `_ - Don't prompt if --override-release-repository-url `#594 `_ 0.10.0 (2020-09-28 16:30:00 -0700) ---------------------------------- - Add CMAKE_PREFIX_PATH to ament_cmake and cmake templates `#606 `_ - Add RHEL to the default action list `#604 `_ - Handle skipping keys multiple times `#603 `_ 0.9.8 (2020-08-21 15:36:00 -0700) --------------------------------- - Run tests in RPM builds for ament and cmake packages. `#585 `_ - Add --skip-keys argument to the RPM generator. `#602 `_ - Fix conditional evaluation of Conflicts/Replaces. `#601 `_ - Set Python2-Depends-Name to allow releasing from Focal. `#595 `_ - Drop CATKIN_BUILD_BINARY_PACKAGE from cmake RPMs. `#589 `_ - Remove catkin reference from comment in non-catkin pkgs. `#588 `_ 0.9.7 (2020-05-04 12:45:00 -0800) --------------------------------- - Removed rmw implementations from typesupport dependencies for Foxy. `#587 `_ 0.9.6 (2020-05-04 05:15:00 -0800) --------------------------------- - Fix a packaging bug affecting only Debian packages 0.9.5 (2020-05-01 15:00:00 -0800) --------------------------------- - Fixed interactive prompt when not running interactively. `#584 `_ - Updated vendor typesupport dependencies for Foxy. `#586 `_ - Happy International Workers' Day. 0.9.4 (2020-04-30 06:30:00 -0800) --------------------------------- - Restored return code of rosdep failure when non-interactive. `#577 `_ - Added the development branch to info log when using version auto-detection. `#579 `_ - Added non-interactive option to git-bloom-release command. `#581 `_ 0.9.3 (2020-03-02 17:30:00 -0800) --------------------------------- - Fix a packaging bug affecting only Debian packages 0.9.2 (2020-03-02 13:00:00 -0800) --------------------------------- - Add option to disable weak RPM dependencies. `#574 `_ - If not interactive, don't prompt to fix broken rosdep keys. `#573 `_ - Make the RPM spec templates look more like the results. `#568 `_ - Suppress automatic rosdep with BLOOM_SKIP_ROSDEP_UPDATE. `#565 `_ - Provide group membership information in RPM packages. `#564 `_ - Add an optional release version suffix to RPM templates. `#569 `_ - Drop Connext from RPM RMW list. `#566 `_ - Provide -devel, -doc and -runtime virtual packages in RPM. `#563 `_ - Print the OS name along with OS version for RPMs. `#562 `_ 0.9.1 (2020-02-03 10:00:00 -0800) --------------------------------- - Added a debinc of 100 to prevent conflicts with upstream Ubuntu versions. - Added version dependency on ``rosdistro`` version ``0.8.0`` to prevent use of out-of-date version which caused pull requests to fail. - Changed to not try and create a pull request if ``--pretend`` is used. 0.9.0 (2019-10-18 14:15:00 -0800) --------------------------------- - Fixed possibly unescaped logger formatting in version. `#553 `_ - Added ament package support to RPM generation. `#534 `_ - Added ROS_PYTHON_VERSION to dependency condtionals. `#551 `_ With this change rosdistro v4 indexes must define the ``python_version`` field introduced in `ros-infrastructure/rep#207 `_ - Changed release suites, dropping all older than Ubuntu Xenial and adding Ubuntu Cosmic, Disco, and Eoan. `#543 `_ - Improved error handling for package conditions and index files. `#537 `_ - Added feature to disable pull request generation via argument or ``BLOOM_NO_ROSDISTRO_PULL_REQUEST`` environment variable. `#548 `_ - Updated RPM generator to always create a source archive. `#540 `_ - Updated RPM spec templates. `#533 `_ 0.8.0 (2019-04-12 13:45:00 -0800) --------------------------------- - Start release increment at 1. `#528 `_ - Evaluate conditions in package.xml before resolving dependencies. `#519 `_ - Update to prevent overwriting template files that exist in source. `#516 `_ - Update debian templates to add trailing newline. `#523 `_ - Fix str/bytes issue in Python 3 auth. `#522 `_ - Use distribution type from index v4 to set ROS 2-specific behavior. `#502 `_ - Fix tests to allow them to run outside of a git context. `#515 `_ - Fix tests to allow empty git environment. `#514 `_ - Invoke scripts using the current python executable. `#513 `_ - Drop support for older distributions. (Retroactive to 0.6.8) `#512 `_ 0.7.2 (2019-01-26 07:45:00 -0800) --------------------------------- - Updated a test to support mixed rosdistro index. `#510 `_ - Updated to use yaml.safe_load for untrusted yaml input. `#508 `_ - Required rosdistro 0.15.0 for package format 3 conditional dependency support. `#511 `_ 0.7.1 (2019-01-11 16:05:00 -0800) --------------------------------- - Fix some bugs from python3 compatibility. `#505 `_ 0.7.0 (2019-01-10 09:45:00 -0800) --------------------------------- - Quote files removed with `git rm -rf`. `#491 `_ - Only consider rosdistros of the same type when looking for a repo name in recent distros. `#501 `_ - Fix python 3 support for pull request generation. `#489 `_ 0.6.9 (2018-11-15 08:45:00 -0800) --------------------------------- - Added Crystal Clemmys (crystal) to the list of ROS 2 rosdistros. `#495 `_ 0.6.8 (2018-11-07 06:45:00 -0800) --------------------------------- - Added support for rosdistro index v4. Bloom now requires rosdistro 0.7.0. `#493 `_ - Refactored for future GitLab pull request support. `#486 `_ - Added basic command execution test. `#487 `_ 0.6.7 (2018-09-24 06:30:00 -0800) --------------------------------- - Added debian/copyright file to debian package when license file is specified in package.xml. `#470 `_ - Refactored release command to prepare for GitLab pull request support. `#483 `_ - Fixed outdated GitHub URL in help text. `#484 `_ - Added entry to tracks.yaml to store the upstream tag of the last release. `#472 `_ 0.6.6 (2018-06-28 19:44:00 -0800) --------------------------------- - Updated vendor typesupport injection for ROS 2. `#477 `_ 0.6.5 (2018-06-25 07:00:00 -0800) --------------------------------- - Added injection of vendor typesupport packages into build deps for ROS 2. `#475 `_ - Updated message wording. `#471 `_ - Updated tested python versions. `#466 `_ 0.6.4 (2018-03-20 13:15:00 -0800) --------------------------------- - Fixed use of non-dependency library. `#468 `_ 0.6.3 (2018-03-09 11:05:00 -0800) --------------------------------- - Released for Debian buster. `#457 `_ - Updated bloom-release: The --track/-t argument is now optional and defaults to the rosdistro. `#459 `_ - Added bouncy to the list of ROS 2 rosdistros. `#462 `_ - Added melodic to the list of rosdistros. `#463 `_ - Added support for releasing repositories with submodules. `#461 `_ - Improved release repository discovery with optional environment variable. `#460 `_ - Fixed python3 encoding issue when processing rpm templates. `#464 `_ 0.6.2 (2018-01-08 13:45:00 -0800) --------------------------------- - Removed test.* subpackages from installation. `#444 `_ - Prepared for release supporting Ubuntu Bionic Beaver. `#452 `_ - Fixed error message when GitHub Multi-Factor auth is enabled. `#451 `_ - Added support for ROS 2 Ardent Apalone. `#453 `_ - Fixed an HTTP/JSON encoding issue in bloom-release for Python 3. `#445 `_ 0.6.1 (2017-10-20 13:45:00 -0800) --------------------------------- - Switched to PyPI JSON API for online updates check. `#438 `_ - Fixed regression in bloom-generate. `#440 `_ - Fixed bloom-release in python3. `#441 `_ 0.6.0 (2017-10-19 10:30:00 -0800) --------------------------------- - Added artful support to release configuration. - Added support for 'unmaintained' package status. `#427 `_ - Fixed prompt for opening a pull request from a fork. `#431 `_ - Fixed UTF-8 encoded text across Python 2 and 3. `#432 `_ - Added support for ament packages on Debian. `#435 `_ 0.5.26 (2017-03-28 6:15:00 -0800) --------------------------------- - Fix default answer to prompt in pull request field. 0.5.25 (2017-02-23 11:45:00 -0800) ---------------------------------- - Added the ``auto-commit`` option to quilt so that ``orig.tar.gz`` are reused release to release. See: `#419 `_ 0.5.24 (2017-02-23 11:45:00 -0800) ---------------------------------- - Fixed the way ros/rosdistro is forked. - Added a ``--native`` option as an alternative to the default ``quilt`` for the Debian format. - Added a prompt to ask users if they want to enable pull request building with the build farm. 0.5.23 (2016-10-25 11:45:00 -0800) ---------------------------------- - Fix to support change in output with git 2.9.3. - Added more detailed message about skipping non-required distributions, e.g. Fedora. 0.5.22 (2016-08-24 13:30:00 -0800) ---------------------------------- - Repository names are now checked for bogus contents, to help detect accidental input. - Fixed to allow use of unicode in the long description. - Fixed a pagination related bug that occurred when trying to find a users rosdistro fork on GitHub. - Updated GitHub interactions to allow for use from behind proxy servers. - Added a new message to help people who have two-factor authentication. 0.5.21 (2016-03-04 18:30:00 -0800) ---------------------------------- - Debian pkg descriptions are now split into a synopsis and long description. - The Conflicts and Replaces fields were moved to the general section in the Debian control file. - Generated RPM's now explicitly set the library directory. - Added option to allow quiet exit when a given OS has no platforms in the rosdistro. - Added new default action item to generate for Debian (e.g. Jessie) in addition to Ubuntu and RPM. - Fixed unnecessary ``!!python/unicode`` tags being put in the tracks.yaml. 0.5.20 (2015-04-23 15:00:00 -0800) ---------------------------------- - Updated conditional for special GitHub commit handling logic to include raw.githubusercontent.com. - Updated GitHub commit handling logic to replace the branch part of the ROS distro index url with the commit for more stability. - Set LC_ALL to C when calling out to ``git`` in order to avoid problems from output in different languages. 0.5.19 (2015-02-23 15:00:00 -0800) ---------------------------------- - Fixed tests so they could be run when multiple remotes were in the local bloom git instance. - Fixed a new PEP8 checker test failure. - Added a conflicts rule between the python3 and python2 .deb of bloom (python-bloom and python3-bloom) since they collide anyways with the installed scripts. - Fixed a bug with Conflicts and Replaces in the debian generator. 0.5.18 (2015-02-09 15:53:10 -0800) ---------------------------------- - Fixed a bug which required a git repo as cwd. 0.5.17 (2015-02-03 15:53:10 -0800) ---------------------------------- - Now notifies about existing patches and ignore files when creating a new track. - Now shows the git remotes before prompting for pushing of the release repository. - Now uses reverse alphabetical ordering when selecting track configuration defaults, the idea is that ROS distributions with larger starting characters are more likely to be newer. - Now guesses the release repository, the doc entry, and the source entry based on other distributions. - Replace ``groovy`` with ``indigo`` in many defaults. - Fixed a bug where whitespace in filenames and trailing ``~``'s caused a release failure. - Now does a check of all rosdep keys before starting the Debian and RPM generators. - Fixed a problem for recovering from platform specific rosdep key errors. - Added options to ``bloom-release`` to override the release repository URL and release repository push URL. - Now checks that all rosdep keys resolve to an installed that matches the default installer, i.e. ``apt`` and not ``pip``. This affectes the Debian and RPM generators. 0.5.16 (2014-12-15 14:30:00 -0700) ---------------------------------- - Hotfix to the Replaces/Conflicts template generation to prevent error causes extra whitespace. See: `#340 `_ 0.5.15 (2014-12-08 12:10:00 -0700) ---------------------------------- - Added support for REP 143 which allows for multiple distribution files, currently bloom uses the last one. - Fix to Python3 support. - ``ROSDISTRO_INDEX_URL``'s which point to githubusercontent.com will also be eligible for pull requests now. - ``-DNDEBUG`` is now added to debian configurations by default. 0.5.14 (2014-11-26 08:10:00 -0700) ---------------------------------- - Hotfix for issue #329 which makes sure no extra new lines are introduced in the debian control file. - Changed RPM build directory to have a more unique name. 0.5.13 (2014-11-24 17:10:00 -0700) ---------------------------------- - Fixed exception from importing ``bloom.logging``. - Debian ``gbp.conf`` now uses ``upstream-tag``. - Fixed a bug which overwrote the user provided debian folder during templating. - Added support for utilizing the Conflicts and Replaces in ``package.xml``'s in the Debian control files. 0.5.12 (2014-09-24 15:28:16 -0700) ---------------------------------- - Pull requests are now opened against the commit from which the rosdistro index file is retrieved. This should address the remaining race condition in bloom allows pull requests which modify other entries. Addresses: `#252 `_ - Pagination is now used when listing branches from GitHub. This addresses an error which occurred when the user had too many branches for page one. Addresses: `#273 `_ - Improved support for unicode in changelogs. Addresses: `#260 `_ - Added checking for .git and https on source and doc urls. Addresses: `#271 `_ - Added check to make sure the release repository and the upstream repository are not the same. Addresses: `#267 `_ - Added a check to make sure the changelog versions are sane with respect to the current version being released. - Users can now skip rpm generation if rosdep keys are missing for fedora only. - Improved error handling when GitHub's two factor authentication is encountered. - Fixed a bug with expanding nested tarball's. - Fixed order of changelogs in rpm generators. - Non-interactive mode now applies to the confirmation for opening a pull request. 0.5.11 (2014-07-24 14:28:03 -0700) ---------------------------------- - Added rosrpm generator to the default list of generators. - Upstream repository url and release repository url are now included in the summaries in pull requests. - Updated the warning about changing track actions to make the transition of rosrpm in the default actions smoother. 0.5.10 (2014-06-16 11:48:51 -0700) ---------------------------------- - Fix cleaning behavior for trim and rebase, #281 - Fix a bug where stdout was getting truncated before a user prompt 0.5.9 (2014-05-22 14:55:59 -0700) --------------------------------- - Revert to deb compat version 7 for Oneric 0.5.8 (2014-05-16 16:17:38 -0700) --------------------------------- - Change deb compat version to 9 in order to get default compiler flags (with optimization) again 0.5.7 (2014-05-08 14:00:00 -0700) --------------------------------- - Add versioned dependency on catkin_pkg 0.2.2 0.5.6 (2014-05-07 17:16:43 -0700) --------------------------------- - When generating Debian and Fedora packaging files, explicitly include buildtool_export_depends with run_depends 0.5.5 (2014-05-01 10:24:31 -0700) --------------------------------- - Add noarch flag to fedora generation for metapackages and packages marked as architecture_independent - Fix the order of the arguments for git-bloom-config copy 0.5.4 (2014-04-11 16:09:00 -0700) --------------------------------- - Fixed a problem with the documentation on readthedocs.org 0.5.3 (2014-04-11 15:51:09 -0700) --------------------------------- - Fixed a bug when handling unicode failed on values which were int's - Removed mention of username and hostname from bloom summaries in the release repo's README.md - Fixed unicode handling in Fedora generation - Modified handling of test dependencies for changes from REP-140 roll out - Removed references to python-distribute in favor of python-setuptools - Changed usuage of rosdep api to work with rosdep >= 0.10.27 0.5.2 (2014-03-04 20:52:09 -0600) --------------------------------- - Pull request titles and body are now santized before printing - Prevent unicode getting into the yaml files - Make license tags required (rpm generation) Source RPMs will not build if the license tag is empty or missing. This will not be a problem for the vast majority of packages in ROS. - Packages are now ordered in changelog summary - Improved unicode support in Python2 - setup environment is now sourced before the install step (debian rules file) 0.5.1 (2014-02-24 16:03:29 -0800) --------------------------------- - fix a bug related to setting the status description 0.5.0 (2014-02-23 21:55:00 -0800) --------------------------------- - OAUTH is now used for creating pull requests. On the first pull request, bloom will ask for your github username and password. Using them it will create an authorization on your behalf and store it in your home folder. Specifically `~/.config/bloom`. From then on, bloom will no longer require your username and password for pull requests. Closed #177 and #170. - Added checks to ensure that for github.com urls provided by users they end in `.git` and are `https://` - Added some fixes and monkey patches to empy to better support unicode in changelogs - Added additionally pull request checks, which should prevent some of the invalid pull requests from being created. - Fixed a bug where packages which were removed from the repository were still getting generated. - Merged preliminary Fedora generation support, provided by @cottsay - Added changelog summaries to pull requests - Added a prompt for users to enter doc, source, and maintenance status when releasing. 0.4.9 (2014-02-06 14:05:47 -0800) --------------------------------- - Fixed another bug for first time releases, specifically first time releases which already have doc or source entries 0.4.8 (2014-01-29 14:19:24 -0600) --------------------------------- - Fixed a bug for first time releases 0.4.7 (2014-01-24 15:50:00 -0800) --------------------------------- - Fix bug in pull request opening with new rosdistro format 0.4.6 (2014-01-24 15:33:00 -0800) --------------------------------- - Updates to support REP-0141 with rosdistro >= 0.3.0 and rosdep >= 0.10.25 - @ahendrix contributed an option for doing ssh key based pull request generation 0.4.5 (2014-01-22 10:58:50 -0800) --------------------------------- - Added Python2/3 bilingual support, bloom should now install and work with Python3 - Added an assertion that the rosdistro version 1 is being used in preperation of REP-0141 roll out - Fixed crash from unicode characters in the changelog - Added assertions about the format of version numbers used - Added check for git submodules, still not supported, but bloom will fail earlier with a better error - Fixed a bug where empty folders containing a .gitignore in the upstream caused bloom to fail 0.4.4 (2013-07-22 17:50:55 -0700) --------------------------------- - Properly handle pagination of github pages #174 - Made the pull request branch names more unique to avoid collisions in parallel releasing situations #178 - Disabled automatic opening of the webbrowser on Linux and added an option to disable it otherwise #162 - Fixed a problem where permissions where lost on templates, this applied specifically to loosing the executable flag on the debian rules file #179 - Only put the first maintainer listed in the debian/control file to prevent lintian errors #183 0.4.3 (2013-07-19 16:37:23 -0700) --------------------------------- - Fixed a bug with creating new tracks - Fixed a bug where the debian changelog would be wrong if a CHANGELOG.rst existed for the package, but there was no entry for this version being release - Fixed a bug where the colorization of the diff could cause a crass to occur - Added a versioned dependency on rosdistro-0.2.12, which addresses a rosdistro file formatting bug - Fixed some issues with the stand alone rosdebian generator - Temporary fix for github pagination problems 0.4.2 (2013-06-27 11:20:25 -0700) --------------------------------- - Improved logging system slightly. - Fixed the way logs are renamed after closing. - Fixed a bug where names were not debian'ized for packages which rosdep could not resolve. #163 - Fixed a bug where a diff of the rosdistro file would fail when packages were being removed. #165 - Fixed a bug where upconverting repository configs could fail if a track.yaml and a bloom.conf existed. #166 0.4.1 (2013-06-25 12:17:13 -0700) --------------------------------- - Fixed a bug which occurred on repositories with no previous releases. #158 - Fixed a bug where safety warnings were being printed when they should not have been. #159 - Fixed a bug where repositories with multiple packages did not consider peer packages when resolving rosdep keys. #160 0.4.0 (2013-06-19 17:13:36 -0700) --------------------------------- - Automated Pull Requests have been re-enabled, but now the .netrc file is **not** used. - REP-0132 CHANGELOG.rst files are now parsed and inserted into generated debian changes files. - bloom now summarizes activity on the master branch, which is useful for figuring out what has been released recently. - There is a new command bloom-generate, which allows generators to expose a stand alone generation command. For example, you can now run ``bloom-generate debian`` in a single catkin package and it will generate the needed files in the local ``debian`` folder. Addresses #121 - The command line options for ``bloom-release`` have been changed to be more explicit. - The ``bloom`` branch is now deprecated, the ``master`` branch now holds all configurations and upstream overlay files. The ``bloom`` branch can be deleted after the automatic upgrade where bloom moves the needed files from the ``bloom`` branch to the ``master`` branch. - Fuerte is no longer supported; this is because supporting fuerte was complicating the code base, use an older version of bloom (0.3.5) for fuerte releases. - Packages can now be explicitly ignored by bloom by putting their names (separated by whitespace) in the .ignored file in the master branch. - Deprecated rosdep calls have been replaced with rosdistro. - bloom now logs all output to log files in the ``~/.bloom_logs`` folder. - Fixed several bugs: - Fixed use of tar as a vcs type #149 - Fixed a bug where ``--new-track``'s changes would not take affect #147 - bloom now allows a debian folder to already exist, overlaying other generated files #146 - bloom now allows for an alternative release repository url which is used for pushing #137 0.3.5 (2013-04-17 11:03:50 -0700) --------------------------------- - Temporarily disable automated pull requests while the new rosdistro format is being deployed. - bloom now suggests likely alternatives when a repository is not found in the release file. 0.3.4 (2013-04-09 16:36:55 -0700) --------------------------------- - Fixed a bug in the update notifier where the first run after updating still reports that bloom is out of date. #129 - bloom-release now respects global arguments like --version and --debug - Improved messages around the cloning/pushing back of the working copy which takes a long time on large repos. - Improved pull request failure message, indicating that the release was successful, but the pr was not. #131 - Fixed versioned dependencies in debians and setup.py. #130 - Fixed a bug with empty ~/.netrc files. #131 - General improvements with the automated pull request mechanism. #135 - Checks for valid metapackages using catkin_pkg now, adds version dependency of catkin_pkg at 0.1.11. #134 0.3.3 (2013-04-01 14:04:00 -0700) --------------------------------- - bloom no longer allows users to release metapackages which do not have a CMakeLists.txt. See: `REP-0127 `_ - Fixed a bug related to gathering of package meta data on hg and svn repositories. #111 - Fixed a bug in git-bloom-patch which prevented users from running it directly. #110 - Fixed a bug where patches would not get applied after exporting them manually. #107 - Worked around a bug in vcstools which would not allow hg repositories to checkout to existing, empty directories. #112 - All git-bloom-* scripts now assert that they are in the root of a git repository. #113 - Added PEP8 check to the automated tests. - bloom-release will now offer the user a git push --force if non-force fails. - Added git-bloom-config [copy|rename] commands. - Fixed a bug in the bloom.git.checkout api where it would return 0 on success, but should return True. #122 - bloom-release will now prompt the user for the release repository url if it is not in the rosdistro file. #125 - bloom-release will now offer to automatically open a pull-request for the user, if the user's .netrc file is setup for github. #126 0.3.2 (2013-03-06 17:49:51 -0800) --------------------------------- - Fixed a bug in vcs url templating. - Improved the performance of git-bloom-config. - Added an --unsafe global option which will disable some of the safety mechanisms in bloom, making releasing about twice as fast but at the expense of errors putting the local release repository in an inconsistent state. Use with caution. - Added support for templating stack.xml files like package.xml files in the import-upstream step. - Fixed a bug where bloom failed if you call it and you were not on a branch - Added global arguments to some commands which were still lacking them - Fixed a bug where bloom would create None/ tags (these should be deleted manually if found) - Got the automated tests fixed and running in travis again - Added emoji icons for OS X users with lion or greater 0.3.1 (2013-02-26 18:00:47 -0800) --------------------------------- - Fixed handling of non-standard archive names in git-bloom-import-upstream. This was a bug where if the archive only had the name of the package then it would fail to be processed by import-upstream. - Fixed an issue when blooming from svn upstream. This issue was caused by improperly handling the release_tag configuration when dealing with svn 0.3.0 (2013-02-26 14:04:21 -0800) --------------------------------- - Generators can now be added using the distribute entry_points machanism - There is now a debian// branch before forking into debian/// The debian/ branch now contains the untemplated debain files, so that they can be patched before being templated. - Users are now dropped into a shell when patch merging fails, allowing them to resolve the problem and continue. - New generator rosrelease, makes the release tag release// instead of release/ - Bloom now checks to see if it is the latest version available and warns if it is not - Configurations are now stored in "tracks" so that there can be multiple release configurations in each release repository - New command bloom-export-upstream, this command creates an archive (tar.gz) of upstream given a uri, type, and reference to archive - Refactored git-bloom-import-upstream, this command only takes an archive (tar.gz) now - Configurations are now stored on the bloom branch in YAML - git-bloom-release now takes only one argument, the release track to execute - Files can be automatically overlaid onto upstream using a patches folder in the bloom branch This allows you to put a package.xml onto upstream without a patch in the release branch. - package.xml files overlaid onto upstream branch from the patches folder in the bloom branch are templated on the version - Release tags now contain release increment numbers, similar to the debian increment numbers, e.g. release/groovy/foo/0.1.0 is now release/groovy/foo/0.1.0-0 - New command bloom-release [], which will release a repository end-to-end It will fetch the release repository using info from the ROS distro file, run bloom, then push the results bloom-0.10.7/LICENSE.txt000066400000000000000000000030771403641720100145370ustar00rootroot00000000000000Software License Agreement (BSD License) Copyright (c) 2013, Willow Garage, Inc. Copyright (c) 2013-2018, Open Source Robotics Foundation, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Willow Garage, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.bloom-0.10.7/MANIFEST.in000066400000000000000000000001511403641720100144400ustar00rootroot00000000000000recursive-include bloom/generators/debian/templates * recursive-include bloom/generators/rpm/templates * bloom-0.10.7/Makefile000077500000000000000000000014261403641720100143530ustar00rootroot00000000000000.PHONY: all clean_dist clean doc VERSION=`./setup.py --version` USERNAME ?= $(shell whoami) UNAME := $(shell uname) doc: python setup.py build_sphinx ifeq ($(UNAME),Darwin) @open doc/build/html/index.html else @echo "Not opening index.html on $(UNAME)" endif all: echo "noop for debbuild" clean_dist: -rm -f MANIFEST -rm -rf dist -rm -rf deb_dist clean: clean_dist echo "clean" NEW_VERSION := $(shell python docs/bump_version.py setup.py --version_only) pre-release: # Bump the version python docs/bump_version.py setup.py > setup.py_tmp mv setup.py_tmp setup.py # Set the permissions chmod 775 setup.py # Commit bump git commit -m "$(NEW_VERSION)" setup.py # Tag it git tag -f $(NEW_VERSION) release: pre-release @echo "Now push the result with git push --all" bloom-0.10.7/README.rst000066400000000000000000000014151403641720100143750ustar00rootroot00000000000000bloom ===== A tool for helping release software into gitbuildpackage repositories Code & tickets -------------- Build status: |Build Status| .. |Build Status| image:: https://secure.travis-ci.org/ros-infrastructure/bloom.png :target: http://travis-ci.org/ros-infrastructure/bloom +---------------+---------------------------------------------------+ | bloom | http://ros.org/wiki/bloom | +---------------+---------------------------------------------------+ | Issues | http://github.com/ros-infrastructure/bloom/issues | +---------------+---------------------------------------------------+ | Documentation | http://ros-infrastructure.github.com/bloom/doc | +---------------+---------------------------------------------------+ bloom-0.10.7/bloom/000077500000000000000000000000001403641720100140155ustar00rootroot00000000000000bloom-0.10.7/bloom/__init__.py000077500000000000000000000003551403641720100161340ustar00rootroot00000000000000try: import pkg_resources try: __version__ = pkg_resources.require("bloom")[0].version except pkg_resources.DistributionNotFound: __version__ = 'unset' except (ImportError, OSError): __version__ = 'unset' bloom-0.10.7/bloom/commands/000077500000000000000000000000001403641720100156165ustar00rootroot00000000000000bloom-0.10.7/bloom/commands/__init__.py000066400000000000000000000034761403641720100177410ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ For each 'command' check for updates... """ from __future__ import print_function import sys if not sys.argv[0].endswith('bloom-update') and 'nosetests' not in sys.argv: from bloom.commands.update import start_updater start_updater() bloom-0.10.7/bloom/commands/export_upstream.py000066400000000000000000000136451403641720100214420ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import binascii import hashlib import os import sys import traceback try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.logging import warning from bloom.git import branch_exists from bloom.git import get_root from bloom.git import tag_exists from bloom.util import add_global_arguments from bloom.util import change_directory from bloom.util import handle_global_arguments from bloom.util import temporary_directory try: from vcstools.vcs_abstraction import get_vcs_client except ImportError: debug(traceback.format_exc()) error("vcstools was not detected, please install it.", file=sys.stderr, exit=True) def get_argument_parser(): parser = argparse.ArgumentParser(description="""\ Creates a tarball from an upstream repository, which can be given to git-bloom-import-upstream to be imported into the release repository. """) add = parser.add_argument add('uri', help="uri of the upstream repository") add('type', choices=['git', 'hg', 'svn'], help="vcs type of upstream repository") add('--tag', '-t', help="release tag to be exported (can be other types of references)") add('--output-dir', '-o', help="destination of the tarball") add('--display-uri', help="uri to use in messages (original upstream)") add('--name', '-n', help="name of the repository being exported (used in tarball name)") return parser def calculate_file_md5(path, block_size=2 ** 20): md5 = hashlib.md5() with open(path, 'rb') as f: while True: data = f.read(block_size) if not data: break md5.update(data) digest = binascii.hexlify(md5.digest()) if not isinstance(digest, str): digest = digest.decode('utf-8') return digest def export_upstream(uri, tag, vcs_type, output_dir, show_uri, name): tag = tag if tag != ':{none}' else None output_dir = output_dir or os.getcwd() if uri.startswith('git@'): uri_is_path = False else: uri_parsed = urlparse(uri) uri = uri if uri_parsed.scheme else uri_parsed.path uri_is_path = False if uri_parsed.scheme else True name = name or 'upstream' with temporary_directory() as tmp_dir: info("Checking out repository at '{0}'".format(show_uri or uri) + (" to reference '{0}'.".format(tag) if tag else '.')) if uri_is_path: upstream_repo = get_vcs_client(vcs_type, uri) else: repo_path = os.path.join(tmp_dir, 'upstream') upstream_repo = get_vcs_client(vcs_type, repo_path) if not upstream_repo.checkout(uri, tag or ''): error("Failed to clone repository at '{0}'".format(uri) + (" to reference '{0}'.".format(tag) if tag else '.'), exit=True) tarball_prefix = '{0}-{1}'.format(name, tag) if tag else name tarball_path = os.path.join(output_dir, tarball_prefix) full_tarball_path = tarball_path + '.tar.gz' info("Exporting to archive: '{0}'".format(full_tarball_path)) if not upstream_repo.export_repository(tag or '', tarball_path): error("Failed to create archive of upstream repository at '{0}'" .format(show_uri)) if tag and vcs_type == 'git': # can only check for git repos with change_directory(upstream_repo.get_path()): if not tag_exists(tag): warning("'{0}' is not a tag in the upstream repository..." .format(tag)) if not branch_exists(tag): warning("'{0}' is not a branch in the upstream repository..." .format(tag)) if not os.path.exists(full_tarball_path): error("Tarball was not created.", exit=True) info("md5: {0}".format(calculate_file_md5(full_tarball_path))) def main(sysargs=None): parser = get_argument_parser() parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) export_upstream(args.uri, args.tag, args.type, args.output_dir, args.display_uri, args.name) bloom-0.10.7/bloom/commands/generate.py000066400000000000000000000064251403641720100177710ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import sys import pkg_resources from bloom.util import add_global_arguments from bloom.util import handle_global_arguments BLOOM_GENERATE_CMDS_GROUP = 'bloom.generate_cmds' def list_generator_commands(): generators = [] for entry_point in pkg_resources.iter_entry_points(group=BLOOM_GENERATE_CMDS_GROUP): generators.append(entry_point.name) return generators def load_generator_description(generator_name): for entry_point in pkg_resources.iter_entry_points(group=BLOOM_GENERATE_CMDS_GROUP): if entry_point.name == generator_name: return entry_point.load() def create_subparsers(parser, generator_cmds): metavar = '[' + ' | '.join(generator_cmds) + ']' subparser = parser.add_subparsers( title='generate commands', metavar=metavar, description='Call `bloom-generate {0} -h` for help on a each generate command.'.format(metavar), dest='generator_cmd' ) for generator_cmd in generator_cmds: desc = load_generator_description(generator_cmd) cmd_parser = subparser.add_parser(desc['title'], description=desc['description']) cmd_parser = desc['prepare_arguments'](cmd_parser) cmd_parser.set_defaults(func=desc['main']) add_global_arguments(cmd_parser) def main(sysargs=None): parser = argparse.ArgumentParser( description="Calls a generator on a local package, e.g. bloom-generate debian" ) generator_cmds = list_generator_commands() create_subparsers(parser, generator_cmds) args = parser.parse_args(sysargs) handle_global_arguments(args) sys.exit(args.func(args) or 0) bloom-0.10.7/bloom/commands/git/000077500000000000000000000000001403641720100164015ustar00rootroot00000000000000bloom-0.10.7/bloom/commands/git/__init__.py000066400000000000000000000000001403641720100205000ustar00rootroot00000000000000bloom-0.10.7/bloom/commands/git/branch.py000077500000000000000000000160221403641720100202140ustar00rootroot00000000000000from __future__ import print_function import argparse from bloom.git import branch_exists from bloom.git import checkout from bloom.git import create_branch from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import ls_tree from bloom.git import tag_exists from bloom.git import track_branches from bloom.logging import ansi from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.logging import log_prefix from bloom.logging import warning from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config from bloom.util import add_global_arguments from bloom.util import handle_global_arguments from bloom.util import maybe_continue @log_prefix('[git-bloom-branch]: ') def execute_branch(src, dst, interactive, directory=None): """ Changes to the destination branch, creates branch and patches/branch if they do not exist. If the dst branch does not exist yet, then it is created by branching the current working branch or the specified SRC_BRANCH. If the patches/dst branch branch does not exist yet then it is created. If the branches are created successful, then the working branch will be set to the dst branch, otherwise the working branch will remain unchanged. :param src: source branch from which to copy :param dst: destination branch :param interactive: if True actions are summarized before committing :param directory: directory in which to preform this action :raises: subprocess.CalledProcessError if any git calls fail """ # Determine if the srouce branch exists if src is None: error("No source specified and/or not a branch currently", exit=True) if branch_exists(src, local_only=False, directory=directory): if not branch_exists(src, local_only=True, directory=directory): debug("Tracking source branch: {0}".format(src)) track_branches(src, directory) elif tag_exists(src): pass else: error("Specified source branch does not exist: {0}".format(src), exit=True) # Determine if the destination branch needs to be created create_dst_branch = False if branch_exists(dst, local_only=False, directory=directory): if not branch_exists(dst, local_only=True, directory=directory): debug("Tracking destination branch: {0}".format(dst)) track_branches(dst, directory) else: create_dst_branch = True # Determine if the destination patches branch needs to be created create_dst_patches_branch = False dst_patches = 'patches/' + dst if branch_exists(dst_patches, False, directory=directory): if not branch_exists(dst_patches, True, directory=directory): track_branches(dst_patches, directory) else: create_dst_patches_branch = True # Summarize if interactive: info("Summary of changes:") if create_dst_branch: info(" " * 22 + "- The specified destination branch, " + ansi('boldon') + dst + ansi('reset') + ", does not exist; it will be created from the source " "branch " + ansi('boldon') + src + ansi('reset')) if create_dst_patches_branch: info(" " * 22 + "- The destination patches branch, " + ansi('boldon') + dst_patches + ansi('reset') + ", does not exist; it will be created") info(" " * 22 + "- The working branch will be set to " + ansi('boldon') + dst + ansi('reset')) if not maybe_continue(): error("Answered no to continue, aborting.", exit=True) # Make changes to the layout current_branch = get_current_branch(directory) try: # Change to the src branch checkout(src, directory=directory) # Create the dst branch if needed if create_dst_branch: create_branch(dst, changeto=True, directory=directory) else: checkout(dst, directory=directory) # Create the dst patches branch if needed if create_dst_patches_branch: create_branch(dst_patches, orphaned=True, directory=directory) # Create the starting config data if it does not exist patches_ls = ls_tree(dst_patches, directory=directory) if 'patches.conf' not in patches_ls: # Patches config not setup, set it up config = { 'parent': src, 'previous': '', 'base': get_commit_hash(dst, directory=directory), 'trim': '', 'trimbase': '' } set_patch_config(dst_patches, config, directory=directory) else: config = get_patch_config(dst_patches, directory=directory) if config['parent'] != src: warning("Updated parent to '{0}' from '{1}'" .format(src, config['parent'])) config['parent'] = src config['base'] = get_commit_hash(dst, directory=directory) set_patch_config(dst_patches, config, directory=directory) # Command successful, do not switch back to previous branch current_branch = None finally: if current_branch is not None: checkout(current_branch, directory=directory) def get_parser(): parser = argparse.ArgumentParser(description="""\ If the DESTINATION_BRANCH does not exist yet, then it is created by branching the current working branch or the specified SOURCE_BRANCH. If the patches/DESTINATION_BRANCH branch does not exist yet then it is created. If the branches are created successful, then the working branch will be set to the DESTINATION_BRANCH, otherwise the working branch will remain unchanged. """, formatter_class=argparse.RawTextHelpFormatter) add = parser.add_argument add('destination_branch', metavar='DESTINATION_BRANCH', help="destination branch name") add('-s', '--src', '--source-branch', metavar='SOURCE_BRANCH', dest='src', help="(optional) specifies which local git branch to branch from") add('-i', '--interactive', dest='interactive', help="asks before committing any changes", action='store_true', default=False) return parser def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() parser = get_parser() parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) # Check that the current directory is a serviceable git/bloom repo try: ensure_clean_working_env() ensure_git_root() except SystemExit: parser.print_usage() raise # If the src argument isn't set, use the current branch if args.src is None: args.src = get_current_branch() # Execute the branching execute_branch(args.src, args.destination_branch, args.interactive) bloom-0.10.7/bloom/commands/git/config.py000077500000000000000000000326071403641720100202330ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import copy import yaml import subprocess import sys from bloom.config import ACTION_LIST_HISTORY from bloom.config import BLOOM_CONFIG_BRANCH from bloom.config import config_template from bloom.config import DEFAULT_TEMPLATE from bloom.config import get_tracks_dict_raw from bloom.config import PromptEntry from bloom.config import upconvert_bloom_to_config_branch from bloom.config import write_tracks_dict_raw from bloom.git import branch_exists from bloom.git import ls_tree from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import get_root from bloom.git import inbranch from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.logging import warning from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import check_output from bloom.util import handle_global_arguments from bloom.util import maybe_continue from bloom.util import safe_input template_entry_order = [ 'name', 'vcs_uri', 'vcs_type', 'version', 'release_tag', 'devel_branch', 'ros_distro', 'patches', 'release_repo_url' ] @inbranch('bloom') def convert_old_bloom_conf(prefix=None): prefix = prefix if prefix is not None else 'convert' tracks_dict = get_tracks_dict_raw() track = prefix track_count = 0 while track in tracks_dict['tracks']: track_count += 1 track = prefix + str(track_count) track_dict = copy.copy(DEFAULT_TEMPLATE) cmd = 'git config -f bloom.conf bloom.upstream' upstream_repo = check_output(cmd, shell=True).strip() cmd = 'git config -f bloom.conf bloom.upstreamtype' upstream_type = check_output(cmd, shell=True).strip() try: cmd = 'git config -f bloom.conf bloom.upstreambranch' upstream_branch = check_output(cmd, shell=True).strip() except subprocess.CalledProcessError: upstream_branch = '' for key in template_entry_order: if key == 'vcs_uri': track_dict[key] = upstream_repo continue if key == 'vcs_type': track_dict[key] = upstream_type continue if key == 'vcs_uri': track_dict[key] = upstream_branch or None continue track_dict[key] = track_dict[key].default debug('Converted bloom.conf:') with open('bloom.conf', 'r') as f: debug(f.read()) debug('To this track:') debug(str({track: track_dict})) tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) execute_command('git rm bloom.conf', shell=True) execute_command('git commit -m "Removed bloom.conf"', shell=True) # Now move the old bloom branch into master upconvert_bloom_to_config_branch() def show_current(): bloom_ls = ls_tree(BLOOM_CONFIG_BRANCH) bloom_files = [f for f, t in bloom_ls.items() if t == 'file'] if 'bloom.conf' in bloom_files: info("Old bloom.conf file detected, up converting...") convert_old_bloom_conf() bloom_ls = ls_tree(BLOOM_CONFIG_BRANCH) bloom_files = [f for f, t in bloom_ls.items() if t == 'file'] if 'tracks.yaml' in bloom_files: info(yaml.dump(get_tracks_dict_raw(), indent=2, default_flow_style=False)) def check_git_init(): if get_root() is None: error("Not in a valid git repository", exit=True) cmd = 'git show-ref --heads' result = execute_command(cmd, autofail=False, silent_error=True) if result != 0: info("Freshly initialized git repository detected.") info("An initial empty commit is going to be made.") if not maybe_continue(): error("Answered no to continue, exiting.", exit=True) # Make an initial empty commit execute_command('git commit --allow-empty -m "Initial commit"', silent=True) def new_cmd(args): new(args.track, args.template) def new(track, template=None, copy_track=None, overrides={}): """ Creates a new track :param track: Name of the track to create :param template: Template to base new track off :param copy_track: Track to copy values of, if '' then use any availabe track if one exists :param overrides: dict of entries to override default values """ tracks_dict = get_tracks_dict_raw() if track in tracks_dict['tracks']: error("Cannot create track '{0}' beause it exists.".format(track)) error("Run `git-bloom-config edit {0}` instead.".format(track), exit=True) track_dict = copy.copy(DEFAULT_TEMPLATE) template_dict = copy.copy(config_template[template]) if copy_track is not None: if template is not None: error("You cannot specify both a template and track to copy.", exit=True) if copy_track == '' and len(tracks_dict['tracks']) != 0: copy_track = list(reversed(sorted(tracks_dict['tracks'].keys())))[0] if copy_track and copy_track not in tracks_dict['tracks']: error("Cannot copy a track which does not exist: '{0}'" .format(copy_track), exit=True) if copy_track: template_dict = tracks_dict['tracks'][copy_track] else: template_dict = {} for key in template_entry_order: if key in template_dict: track_dict[key].default = template_dict[key] if key in overrides: track_dict[key].default = overrides[key] if track_dict[key].default == ':{name}': track_dict[key].default = track ret = safe_input(str(track_dict[key])) if ret: track_dict[key].default = ret # This type checks against self.values if ret in [':{none}', 'None']: track_dict[key].default = None track_dict[key] = track_dict[key].default tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) info("Created '{0}' track.".format(track)) def show(args): tracks_dict = get_tracks_dict_raw() if args.track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(args.track), exit=True) info(yaml.dump({args.track: tracks_dict['tracks'][args.track]}, indent=2, default_flow_style=False)) def update_track(track_dict): for key, value in DEFAULT_TEMPLATE.items(): if key in ['actions']: if track_dict[key] != DEFAULT_TEMPLATE[key]: warning("Your track's '{0}' configuration is not the same as the default." .format(key)) default = 'n' if key == 'actions' and track_dict[key] in ACTION_LIST_HISTORY: default = 'y' warning("Unless you have manually modified your 'actions' " "(the commands which get run for a release), " "you should update to the new default.") warning("Should it be updated to the default setting?") if maybe_continue(default): track_dict[key] = DEFAULT_TEMPLATE[key] elif key not in track_dict: value = value.default if isinstance(value, PromptEntry) else value track_dict[key] = value return track_dict def edit_cmd(args): edit(args.track) def edit(track): tracks_dict = get_tracks_dict_raw() if track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(track), exit=True) # Ensure the track is complete track_dict = tracks_dict['tracks'][track] update_track(track_dict) # Prompt for updates for key in template_entry_order: pe = DEFAULT_TEMPLATE[key] pe.default = tracks_dict['tracks'][track][key] ret = safe_input(str(pe)) if ret: pe.default = ret # This type checks against self.values if ret in [':{none}', 'None']: pe.default = None tracks_dict['tracks'][track][key] = pe.default tracks_dict['tracks'][track] = track_dict info("Saving '{0}' track.".format(track)) write_tracks_dict_raw(tracks_dict) def delete(args): delete_cmd(args.track) def delete_cmd(track): tracks_dict = get_tracks_dict_raw() if track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(track), exit=True) del tracks_dict['tracks'][track] info("Deleted track '{0}'.".format(track)) write_tracks_dict_raw(tracks_dict) def copy_cmd(args): copy_track(args.src, args.dst) def copy_track(src, dst): tracks_dict = get_tracks_dict_raw() if src not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(src), exit=True) if dst in tracks_dict['tracks']: error("Track '{0}' already exists.".format(dst), exit=True) tracks_dict['tracks'][dst] = copy.deepcopy(tracks_dict['tracks'][src]) info("Saving '{0}' track.".format(dst)) write_tracks_dict_raw(tracks_dict) def rename_cmd(args): rename_track(args.src, args.dst) def rename_track(src, dst): copy_track(src, dst) delete_cmd(src) def get_argument_parser(): parser = argparse.ArgumentParser(description="""\ Configures the bloom repository with information in groups called tracks. """) metavar = "[new|show|edit|delete|copy|rename]" subparsers = parser.add_subparsers( title="Commands", metavar=metavar, description="""\ Call `git-bloom-config {0} -h` for additional help information on each command. """.format(metavar)) new_parser = subparsers.add_parser('new') add = new_parser.add_argument add('track', help="name of track to create") add('--template', '-t', help="tempate to base new track on", choices=[x for x in config_template.keys() if x is not None], default=None) new_parser.set_defaults(func=new_cmd) new_parser = subparsers.add_parser('show') add = new_parser.add_argument add('track', help="name of track to show") new_parser.set_defaults(func=show) new_parser = subparsers.add_parser('edit') add = new_parser.add_argument add('track', help="name of track to edit") new_parser.set_defaults(func=edit_cmd) new_parser = subparsers.add_parser('delete') add = new_parser.add_argument add('track', help="name of track to delete") new_parser.set_defaults(func=delete) new_parser = subparsers.add_parser('copy') add = new_parser.add_argument add('src', help="name of track to copy from") add('dst', help="name of track to copy to") new_parser.set_defaults(func=copy_cmd) new_parser = subparsers.add_parser('rename') add = new_parser.add_argument add('src', help="name of track to rename") add('dst', help="new name of track") new_parser.set_defaults(func=rename_cmd) return parser def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() if len(sysargs if sysargs is not None else sys.argv[1:]) == 0: # This means show me the current config, first check we have an env ensure_clean_working_env() if not branch_exists(BLOOM_CONFIG_BRANCH): sys.exit("No {0} branch found".format(BLOOM_CONFIG_BRANCH)) show_current() info("See: 'git-bloom-config -h' on how to change the configs") return 0 parser = get_argument_parser() add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) # Also check to see if git has been init'ed check_git_init() # Check that the current directory is a serviceable git/bloom repo try: ensure_clean_working_env() ensure_git_root() except SystemExit: parser.print_usage() raise # Then call the verb try: args.func(args) except (KeyboardInterrupt, EOFError): error("\nUser sent a Keyboard Interrupt, aborting.", exit=True) bloom-0.10.7/bloom/commands/git/generate.py000066400000000000000000000217611403641720100205540ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import sys import traceback from subprocess import CalledProcessError from bloom.commands.git.branch import execute_branch from bloom.generators import GeneratorError from bloom.generators import list_generators from bloom.generators import load_generator from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import GitClone from bloom.logging import debug from bloom.logging import error # from bloom.logging import info from bloom.logging import log_prefix from bloom.logging import warning from bloom.commands.git.patch.export_cmd import export_patches from bloom.commands.git.patch.import_cmd import import_patches from bloom.commands.git.patch.rebase_cmd import rebase_patches from bloom.util import add_global_arguments from bloom.util import code from bloom.util import handle_global_arguments from bloom.util import maybe_continue from bloom.util import print_exc class CommandFailed(Exception): def __init__(self, returncode): self.returncode = returncode def parse_branch_args(branch_args, interactive): if type(branch_args) not in [list, tuple] or \ len(branch_args) not in [1, 2, 3]: error("Invalid branching arguments given: '" + str(branch_args) + "'") raise GeneratorError(code.INVALID_BRANCH_ARGS) blen = len(branch_args) # Get branching parameters: dest = branch_args[0] source = branch_args[1] if blen >= 2 else None interactive = interactive if blen == 3 and branch_args[2] is not None: interactive = branch_args[2] return dest, source, interactive def summarize_branch_cmd(destination, source, interactive): msg = "Executing 'git-bloom-branch " + str(destination) if source is not None: msg += " --src " + str(source) if interactive: msg += " --interactive" msg += "'" return msg def try_execute(msg, err_msg, func, *args, **kwargs): retcode = 0 try: retcode = func(*args, **kwargs) retcode = retcode if retcode is not None else 0 except CalledProcessError as err: print_exc(traceback.format_exc()) error("Error calling {0}: {1}".format(msg, str(err))) retcode = err.returncode ret_msg = msg + " returned exit code ({0})".format(str(retcode)) if retcode > 0: error(ret_msg) raise CommandFailed(retcode) elif retcode < 0: debug(ret_msg) return retcode def run_generator(generator, arguments): try: gen = generator try_execute('generator handle arguments', '', gen.handle_arguments, arguments) try_execute('generator summarize', '', gen.summarize) if arguments.interactive: if not maybe_continue('y'): error("Answered no to continue, aborting.", exit=True) try_execute('generator pre_modify', '', gen.pre_modify) for branch_args in generator.get_branching_arguments(): parsed_branch_args = parse_branch_args(branch_args, arguments.interactive) destination, source, interactive = parsed_branch_args # Summarize branch command msg = summarize_branch_cmd(destination, source, interactive) # Run pre - branch - post # Pre branch try_execute('generator pre_branch', msg, gen.pre_branch, destination, source) # Branch try_execute('git-bloom-branch', msg, execute_branch, source, destination, interactive) # Post branch try_execute('generator post_branch', msg, gen.post_branch, destination, source) # Run pre - export patches - post # Pre patch try_execute('generator pre_export_patches', msg, gen.pre_export_patches, destination) # Export patches try_execute('git-bloom-patch export', msg, export_patches) # Post branch try_execute('generator post_export_patches', msg, gen.post_export_patches, destination) # Run pre - rebase - post # Pre rebase try_execute('generator pre_rebase', msg, gen.pre_rebase, destination) # Rebase ret = try_execute('git-bloom-patch rebase', msg, rebase_patches) # Post rebase try_execute('generator post_rebase', msg, gen.post_rebase, destination) # Run pre - import patches - post # Pre patch try_execute('generator pre_patch', msg, gen.pre_patch, destination) if ret == 0: # Import patches try_execute('git-bloom-patch import', msg, import_patches) elif ret < 0: debug("Skipping patching because rebase did not run.") # Post branch try_execute('generator post_patch', msg, gen.post_patch, destination) except CommandFailed as err: sys.exit(err.returncode or 1) def create_subparsers(parent_parser, generators): subparsers = parent_parser.add_subparsers( title='generators', description='Available bloom platform generators:', dest='generator' ) for generator in generators: parser = subparsers.add_parser(generator.title, description=generator.description, help=generator.help) generator.prepare_arguments(parser) add_global_arguments(parser) def create_generators(generator_names): generators = {} for generator_name in generator_names: generator = load_generator(generator_name) if generator is not None: generators[generator_name] = generator() else: warning("Failed to load generator: " + str(generator_name)) return generators def get_parser(): parser = argparse.ArgumentParser(description="bloom platform generator") group = parser.add_argument_group('common generator parameters') add = group.add_argument add('-y', '--non-interactive', default=True, action='store_false', help="runs without user interaction", dest='interactive') return parser def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() parser = get_parser() parser = add_global_arguments(parser) # List the generators generator_names = list_generators() # Create the generators generators = create_generators(generator_names) # Setup a subparser for each generator create_subparsers(parser, generators.values()) args = parser.parse_args(sysargs) handle_global_arguments(args) generator = generators[args.generator] # Check that the current directory is a serviceable git/bloom repo try: ensure_clean_working_env() ensure_git_root() except SystemExit: parser.print_usage() raise # Run the generator that was selected in a clone # The clone protects the release repo state from mid change errors with log_prefix('[git-bloom-generate {0}]: '.format(generator.title)): git_clone = GitClone() with git_clone: run_generator(generator, args) git_clone.commit() bloom-0.10.7/bloom/commands/git/import_upstream.py000077500000000000000000000402201403641720100222060ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import os import shutil import sys import tarfile import tempfile from pkg_resources import parse_version try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from bloom.config import BLOOM_CONFIG_BRANCH from bloom.git import branch_exists from bloom.git import create_branch from bloom.git import create_tag from bloom.git import delete_remote_tag from bloom.git import delete_tag from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import get_last_tag_by_version from bloom.git import GitClone from bloom.git import has_changes from bloom.git import inbranch from bloom.git import ls_tree from bloom.git import show from bloom.git import tag_exists from bloom.git import track_branches from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import warning from bloom.packages import get_package_data from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import get_git_clone_state from bloom.util import handle_global_arguments from bloom.util import load_url_to_file_handle def version_check(version): last_tag = get_last_tag_by_version() if not last_tag: return last_tag_version = last_tag.split('/')[-1] info(fmt("The latest upstream tag in the release repository is '@!{0}@|'." .format(last_tag.replace('@', '@@')))) # Ensure the new version is greater than the last tag if parse_version(version) < parse_version(last_tag_version): warning("""\ Version discrepancy: The upstream version '{0}' isn't newer than upstream version '{1}'. """.format(version, last_tag_version)) def import_tarball(tarball_path, target_branch, version, name): if tarball_path.endswith('.zip'): error("Zip archives are not yet supported.", exit=True) # Create the tarfile handle targz = tarfile.open(tarball_path, 'r:gz') with inbranch(target_branch): # Prepare list of members to extract, ignoring some ignores = ('.git', '.gitignore', '.svn', '.hgignore', '.hg', 'CVS') members = targz.getmembers() members = [m for m in members if m.name.split('/')[-1] not in ignores] # Clear out the local branch items = [] for item in os.listdir(os.getcwd()): if item in ['.git', '..', '.']: continue items.append(item) if len(items) > 0: execute_command('git rm -rf ' + ' '.join(['"%s"' % i for i in items if i])) # Clear out any untracked files execute_command('git clean -fdx') # Extract the tarball into the clean branch targz.extractall(os.getcwd(), members) # Check for folder nesting (mostly hg) items = [] for item in os.listdir(os.getcwd()): if not item.startswith('.'): items.append(item) tarball_prefix = os.path.basename(tarball_path)[:-len('.tag.gz')] if [tarball_prefix] == items: debug('Removing nested tarball folder: ' + str(tarball_prefix)) tarball_prefix_path = os.path.join(os.getcwd(), tarball_prefix) for item in os.listdir(tarball_prefix_path): if item in ['.', '..']: continue item_path = os.path.join(os.getcwd(), tarball_prefix, item) debug( 'moving ' + str(item_path) + ' to ' + str(os.path.join(os.getcwd(), item)) ) shutil.move(item_path, os.path.join(os.getcwd(), item)) shutil.rmtree(tarball_prefix_path) else: debug('No nested tarball folder found.') # Commit changes to the repository items = [] for item in os.listdir(os.getcwd()): if item in ['.git', '..', '.']: continue items.append(item) if len(items) > 0: execute_command('git add ' + ' '.join(['"%s"' % i for i in items if i])) # Remove any straggling untracked files execute_command('git clean -dXf') # Only if we have local changes commit # (not true if the upstream didn't change any files) if has_changes(): msg = "Imported upstream version '{0}' of '{1}'" msg = msg.format(version, name or 'upstream') cmd = 'git commit -m "{0}"'.format(msg) execute_command(cmd) # with inbranch(target_branch): def handle_tree(tree, directory, root_path, version): for path, kind in tree.items(): if kind == 'directory': # Path relative to start path rel_path = os.path.join(directory, path) # If it is a file, error if os.path.isfile(rel_path): error("In patches path '{0}' is a directory".format(rel_path) + ", but it exists in the upstream branch as a file.", exit=True) # If it is not already a directory, create it if not os.path.isdir(rel_path): info(" Createing directory... '{0}'".format(rel_path)) os.mkdir(rel_path) # Recurse on the directory handle_tree(ls_tree(BLOOM_CONFIG_BRANCH, os.path.join(root_path, rel_path)), rel_path, root_path, version) if kind == 'file': # Path relative to start path rel_path = os.path.join(directory, path) # If the local version is a directory, error if os.path.isdir(rel_path): error("In patches path '{0}' is a file, ".format(rel_path) + "but it exists in the upstream branch as a directory.", exit=True) # If the file already exists, warn if os.path.isfile(rel_path): warning(" File '{0}' already exists, overwriting..." .format(rel_path)) execute_command('git rm {0}'.format(rel_path), shell=True) # If package.xml tempalte in version, else grab data if path in ['stack.xml']: warning(" Skipping '{0}' templating, fuerte not supported" .format(rel_path)) if path in ['package.xml']: info(" Templating '{0}' into upstream branch..." .format(rel_path)) file_data = show(BLOOM_CONFIG_BRANCH, os.path.join(root_path, rel_path)) file_data = file_data.replace(':{version}', version) else: info(" Overlaying '{0}' into upstream branch..." .format(rel_path)) file_data = show(BLOOM_CONFIG_BRANCH, os.path.join(root_path, rel_path)) # Write file with open(rel_path, 'wb') as f: # Python 2 will treat this as an ascii string but # Python 3 will not re-decode a utf-8 string. if sys.version_info.major == 2: file_data = file_data.decode('utf-8').encode('utf-8') else: file_data = file_data.encode('utf-8') f.write(file_data) # Add it with git execute_command('git add {0}'.format(rel_path), shell=True) def import_patches(patches_path, patches_path_dict, target_branch, version): info("Overlaying files from patched folder '{0}' on the '{2}' branch into the '{1}' branch..." .format(patches_path, target_branch, BLOOM_CONFIG_BRANCH)) with inbranch(target_branch): handle_tree(patches_path_dict, '', patches_path, version) cmd = ('git commit --allow-empty -m "Overlaid patches from \'{0}\'"' .format(patches_path)) execute_command(cmd, shell=True) def import_upstream(tarball_path, patches_path, version, name, replace): # Check for a url and download it url = urlparse(tarball_path) if url.scheme: # Some scheme like http, https, or file... tmp_dir = tempfile.mkdtemp() try: info("Fetching file from url: '{0}'".format(tarball_path)) req = load_url_to_file_handle(tarball_path) tarball_path = os.path.join(tmp_dir, os.path.basename(url.path)) with open(tarball_path, 'wb') as f: chunk_size = 16 * 1024 while True: chunk = req.read(chunk_size) if not chunk: break f.write(chunk) return import_upstream(tarball_path, patches_path, version, name, replace) finally: shutil.rmtree(tmp_dir) # If there is not tarball at the given path, fail if not os.path.exists(tarball_path): error("Specified archive does not exists: '{0}'".format(tarball_path), exit=True) # If either version or name are not provided, guess from archive name if not version or not name: # Parse tarball name tarball_file = os.path.basename(tarball_path) ending = None if tarball_file.endswith('.tar.gz'): ending = '.tar.gz' elif tarball_file.endswith('.zip'): ending = '.zip' else: error("Cannot detect type of archive: '{0}'" .format(tarball_file), exit=True) tarball_file = tarball_file[:-len(ending)] split_tarball_file = tarball_file.split('-') if len(split_tarball_file) < 2 and not version or len(split_tarball_file) < 1: error("Cannot detect name and/or version from archive: '{0}'" .format(tarball_file), exit=True) if not name and len(split_tarball_file) == 1: name = split_tarball_file[0] elif not name and len(split_tarball_file) == 1: name = '-'.join(split_tarball_file[:-1]) if not version and len(split_tarball_file) < 2: error("Cannot detect version from archive: '{0}'" .format(tarball_file) + " and the version was not spcified.", exit=True) version = version if version else split_tarball_file[-1] # Check if the patches_path (if given) exists patches_path_dict = None if patches_path: patches_path_dict = ls_tree(BLOOM_CONFIG_BRANCH, patches_path) if not patches_path_dict: error("Given patches path '{0}' does not exist in bloom branch." .format(patches_path), exit=True) # Do version checking version_check(version) # Check for existing tags upstream_tag = 'upstream/{0}'.format(version) if tag_exists(upstream_tag): if not replace: error("Tag '{0}' already exists, use --replace to override it." .format(upstream_tag), exit=True) warning("Removing tag: '{0}'".format(upstream_tag)) delete_tag(upstream_tag) if not get_git_clone_state(): delete_remote_tag(upstream_tag) name_tag = '{0}/{1}'.format(name or 'upstream', version) if name_tag != upstream_tag and tag_exists(name_tag): if not replace: error("Tag '{0}' already exists, use --replace to override it." .format(name_tag), exit=True) warning("Removing tag: '{0}'".format(name_tag)) delete_tag(name_tag) if not get_git_clone_state(): delete_remote_tag(name_tag) # If there is not upstream branch, create one if not branch_exists('upstream'): info("Creating upstream branch.") create_branch('upstream', orphaned=True) else: track_branches(['upstream']) # Import the given tarball info("Importing archive into upstream branch...") import_tarball(tarball_path, 'upstream', version, name) # Handle patches_path if patches_path: import_patches(patches_path, patches_path_dict, 'upstream', version) # Create tags with inbranch('upstream'): # Assert packages in upstream are the correct version _, actual_version, _ = get_package_data('upstream') if actual_version != version: error("The package(s) in upstream are version '{0}', but the version to be released is '{1}', aborting." .format(actual_version, version), exit=True) # Create the tag info("Creating tag: '{0}'".format(upstream_tag)) create_tag(upstream_tag) if name_tag != upstream_tag: info("Creating tag: '{0}'".format(name_tag)) create_tag(name_tag) def get_argument_parser(): parser = argparse.ArgumentParser(description="""\ Imports a given archive into the release repository's upstream branch. The upstream is cleared of all files, then the archive is extracted into the upstream branch. If a patches_path is given then the contents of that folder are overlaid onto the upstream branch, and any package.xml files are templated on the version. The patches_path must exist in the bloom branch of the local repository. Then the 'upstream-' tag is created. If a repository name is given (or guessed from the archive), a '-' tag is also created. This command must be run in a clean git environment, i.e. no untracked or uncommitted local changes. """) add = parser.add_argument add('archive_path', help="path or url to the archive to be imported") add('patches_path', nargs='?', default='', help="relative path in the '{0}' branch to a folder to be" .format(BLOOM_CONFIG_BRANCH) + " overlaid after import of upstream sources (optional)") add('-v', '--release-version', help="version being imported (defaults to guessing from archive name)") add('-n', '--name', help="name of the repository being imported " "(defaults to guessing from archive name)") add('-r', '--replace', action="store_true", help="""\ allows replacement of an existing upstream import of the same version """) return parser def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() parser = get_argument_parser() parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) # Check that the current directory is a serviceable git/bloom repo try: ensure_clean_working_env() ensure_git_root() except SystemExit: parser.print_usage() raise git_clone = GitClone() with git_clone: import_upstream( args.archive_path, args.patches_path, args.release_version, args.name, args.replace) git_clone.commit() info("I'm happy. You should be too.") bloom-0.10.7/bloom/commands/git/patch/000077500000000000000000000000001403641720100175005ustar00rootroot00000000000000bloom-0.10.7/bloom/commands/git/patch/__init__.py000077500000000000000000000000171403641720100216120ustar00rootroot00000000000000# Patch module bloom-0.10.7/bloom/commands/git/patch/common.py000077500000000000000000000050251403641720100213470ustar00rootroot00000000000000from __future__ import print_function import os import subprocess import traceback from bloom.git import has_changes from bloom.git import inbranch from bloom.git import show from bloom.logging import error from bloom.util import execute_command from bloom.util import print_exc _patch_config_keys = [ 'parent', # The name of the parent reference 'previous', # Parent commit hash, used to check if rebase is needed 'base', # Commit hash before patches 'trim', # Trim sub folder name 'trimbase' # Commit hash before trimming ] _patch_config_keys.sort() def list_patches(directory=None): directory = directory if directory else '.' files = os.listdir(directory) patches = [] for f in files: if f.endswith('.patch'): patches.append(f) return patches def get_patch_config(patches_branch, directory=None): config_str = show(patches_branch, 'patches.conf') if config_str is None: error("Failed to get patches info: patches.conf does not exist") return None lines = config_str.splitlines() meta = {} for key in _patch_config_keys: meta[key] = '' for line in lines: if line.count('=') == 0: continue key, value = line.split('=', 1) meta[key.strip()] = value.strip() return meta def set_patch_config(patches_branch, config, directory=None): @inbranch(patches_branch, directory=directory) def fn(config): global _patch_config_keys conf_path = 'patches.conf' if directory is not None: conf_path = os.path.join(directory, conf_path) config_keys = list(config.keys()) config_keys.sort() if _patch_config_keys != config_keys: raise RuntimeError("Invalid config passed to set_patch_config") cmd = 'git config -f {0} patches.'.format(conf_path) try: for key in config: _cmd = cmd + key + ' "' + config[key] + '"' execute_command(_cmd, cwd=directory) # Stage the patches.conf file cmd = 'git add ' + conf_path execute_command(cmd, cwd=directory) if has_changes(directory): # Commit the changed config file cmd = 'git commit -m "Updated patches.conf"' execute_command(cmd, cwd=directory) except subprocess.CalledProcessError as err: print_exc(traceback.format_exc()) error("Failed to set patches info: " + str(err)) raise return fn(config) bloom-0.10.7/bloom/commands/git/patch/export_cmd.py000077500000000000000000000063401403641720100222240ustar00rootroot00000000000000from __future__ import print_function from bloom.git import branch_exists from bloom.git import checkout from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import get_current_branch from bloom.git import has_changes from bloom.logging import debug from bloom.logging import error from bloom.logging import log_prefix from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import handle_global_arguments from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import list_patches @log_prefix('[git-bloom-patch export]: ') def export_patches(directory=None): # Ensure a clean/valid working environment ensure_clean_working_env(git_status=True, directory=directory) # Get current branch current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) # Construct the patches branch name patches_branch = 'patches/' + current_branch # Ensure the patches branch exists if not branch_exists(patches_branch, False, directory=directory): error("The patches branch ({0}) does not ".format(patches_branch) + "exist, did you use git-bloom-branch?", exit=True) try: # Get parent branch and base commit from patches branch config = get_patch_config(patches_branch, directory) if config is None: error("Failed to get patches information.", exit=True) # Checkout to the patches branch checkout(patches_branch, directory=directory) # Notify the user debug("Exporting patches from " "{0}...{1}".format(config['base'], current_branch)) # Remove all the old patches if len(list_patches(directory)) > 0: cmd = 'git rm ./*.patch' execute_command(cmd, cwd=directory) # Create the patches using git format-patch cmd = "git format-patch -M -B " \ "{0}...{1}".format(config['base'], current_branch) execute_command(cmd, cwd=directory) # Report of the number of patches created patches_list = list_patches(directory) debug("Created {0} patches".format(len(patches_list))) # Clean up and commit if len(patches_list) > 0: cmd = 'git add ./*.patch' execute_command(cmd, cwd=directory) if has_changes(directory): cmd = 'git commit -m "Updating patches."' execute_command(cmd, cwd=directory) finally: if current_branch: checkout(current_branch, directory=directory) def add_parser(subparsers): """Returns a parser.ArgumentParser with all arguments defined""" parser = subparsers.add_parser( 'export', description="""\ Exports the commits that have been made on the current branch since the original source branch (source branch from git-bloom-branch) to a patches branch, which is named 'patches/', using git format-patch. """ ) parser.set_defaults(func=main) add_global_arguments(parser) return parser def main(args): handle_global_arguments(args) ensure_git_root() return export_patches() bloom-0.10.7/bloom/commands/git/patch/import_cmd.py000077500000000000000000000124611403641720100222160ustar00rootroot00000000000000from __future__ import print_function import sys import os import tempfile import shutil import subprocess from bloom.git import branch_exists from bloom.git import checkout from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import track_branches from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.logging import log_prefix from bloom.logging import warning from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import handle_global_arguments from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import list_patches @log_prefix('[git-bloom-patch import]: ') def import_patches(directory=None): # Get current branch current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) # Construct the patches branch name patches_branch = 'patches/' + current_branch # Ensure the patches branch exists and is tracked if branch_exists(patches_branch, False, directory=directory): if not branch_exists(patches_branch, True, directory=directory): track_branches(patches_branch, directory) else: error("The patches branch ({0}) does not ".format(patches_branch) + "exist, did you use git-bloom-branch?", exit=True) # Create a swap space tmp_dir = tempfile.mkdtemp() try: # Get parent branch and base commit from patches branch config = get_patch_config(patches_branch, directory) parent_branch, commit = config['parent'], config['base'] if commit != get_commit_hash(current_branch, directory): debug( "commit != get_commit_hash(current_branch, directory)" ) debug( "{0} != get_commit_hash({1}, {2}) != {3}".format( commit, current_branch, directory, get_commit_hash(current_branch, directory) ) ) os.system('git log') warning( "The current commit is not the same as the most recent " "rebase commit." ) warning( "This might mean that you have committed since the last " "time you did:" ) warning( " 'git-bloom-patch rebase' or 'git-bloom-patch remove'" ) warning( "Make sure you export any commits you want to save first:" ) warning(" 'git-bloom-patch export'") error("Patches not exported", exit=True) # Checkout to the patches branch checkout(patches_branch, directory=directory) # Copy the patches to a temp location patches = list_patches(directory) if len(patches) == 0: debug("No patches in the patches branch, nothing to do") return -1 # Indicates that nothing was done tmp_dir_patches = [] for patch in patches: tmp_dir_patches.append(os.path.join(tmp_dir, patch)) if directory is not None: patch = os.path.join(directory, patch) shutil.copy(patch, tmp_dir) # Now checkout back to the original branch and import them checkout(current_branch, directory=directory) try: cmd = 'git am {0}*.patch'.format(tmp_dir + os.sep) execute_command(cmd, cwd=directory) except subprocess.CalledProcessError as e: warning("Failed to apply one or more patches for the " "'{0}' branch.".format(str(e))) info('', use_prefix=False) info('', use_prefix=False) info(">>> Resolve any conflicts and when you have resolved this " "problem run 'git am --resolved' and then exit the " "shell using 'exit 0'. <<<", use_prefix=False) info(" To abort use 'exit 1'", use_prefix=False) if 'bash' in os.environ['SHELL']: ret = subprocess.call([ "/bin/bash", "-l", "-c", """\ /bin/bash --rcfile <(echo "if [ -f /etc/bashrc ]; then source /etc/bashrc; fi; \ if [ -f ~/.bashrc ]; then source ~/.bashrc; fi;PS1='(bloom)$PS1'") -i""" ]) else: ret = subprocess.call("$SHELL", shell=True) if ret != 0: error("User failed to resolve patch conflicts, exiting.") sys.exit("'git-bloom-patch import' aborted.") info("User reports that conflicts have been resolved, continuing.") # Notify the user info("Applied {0} patches".format(len(patches))) finally: if current_branch: checkout(current_branch, directory=directory) if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) def add_parser(subparsers): parser = subparsers.add_parser( 'import', description="""\ Imports the patches from the patches branch, which is named 'patches/', onto the current working branch. """ ) parser.set_defaults(func=main) add_global_arguments(parser) return parser def main(args): handle_global_arguments(args) return import_patches() bloom-0.10.7/bloom/commands/git/patch/patch_main.py000077500000000000000000000036731403641720100221710ustar00rootroot00000000000000from __future__ import print_function import argparse import sys import traceback from subprocess import CalledProcessError from bloom.git import ensure_git_root from bloom.git import get_root from bloom.logging import error from bloom.util import add_global_arguments from bloom.util import handle_global_arguments from bloom.util import print_exc from bloom.commands.git.patch import export_cmd from bloom.commands.git.patch import import_cmd from bloom.commands.git.patch import remove_cmd from bloom.commands.git.patch import rebase_cmd from bloom.commands.git.patch import trim_cmd def get_argument_parser(): parser = argparse.ArgumentParser( description="Configures the bloom repository with information in groups called tracks.") metavar = "[export|import|remove|rebase|trim]" subparsers = parser.add_subparsers( title="Commands", metavar=metavar, description="Call `git-bloom-patch {0} -h` for additional help information on each command.".format(metavar)) export_cmd.add_parser(subparsers) import_cmd.add_parser(subparsers) remove_cmd.add_parser(subparsers) rebase_cmd.add_parser(subparsers) trim_cmd.add_parser(subparsers) return parser def main(sysargs=None): parser = get_argument_parser() add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) retcode = "command not run" if get_root() is None: parser.print_help() error("This command must be run in a valid git repository.", exit=True) ensure_git_root() try: retcode = args.func(args) or 0 except CalledProcessError as err: # Problem calling out to git probably print_exc(traceback.format_exc()) error(str(err)) retcode = 2 except Exception as err: # Unhandled exception, print traceback print_exc(traceback.format_exc()) error(str(err)) retcode = 3 sys.exit(retcode) bloom-0.10.7/bloom/commands/git/patch/rebase_cmd.py000077500000000000000000000142441403641720100221460ustar00rootroot00000000000000from __future__ import print_function import os import argparse import shutil from tempfile import mkdtemp from bloom.git import ensure_clean_working_env from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import get_root from bloom.git import has_changes from bloom.git import inbranch from bloom.logging import ansi from bloom.logging import debug from bloom.logging import error from bloom.logging import log_prefix from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import handle_global_arguments from bloom.util import my_copytree def non_git_rebase(upstream_branch, directory=None): # Create a temporary storage directory tmp_dir = mkdtemp() # Get the root of the git repository git_root = get_root(directory) try: # Copy the new upstream source into the temporary directory with inbranch(upstream_branch): ignores = ('.git', '.gitignore', '.svn', '.hgignore', '.hg', 'CVS') parent_source = os.path.join(tmp_dir, 'parent_source') my_copytree(git_root, parent_source, ignores) # Clear out any untracked files execute_command('git clean -fdx', cwd=directory) # for good measure? # Collect files (excluding .git) items = [] for item in os.listdir(git_root): if item in ['.git', '..', '.']: continue items.append(item) # Remove all files if len(items) > 0: execute_command('git rm -rf ' + ' '.join(['"%s"' % i for i in items if i]), cwd=directory) # Copy the parent source into the newly cleaned directory my_copytree(parent_source, git_root) # Commit changes to the repository execute_command('git add ./*', cwd=directory) # Collect .* files dot_items = [] for item in os.listdir(git_root): if item in ['.git', '..', '.']: continue if item.startswith('.'): dot_items.append(item) # Add any .* files missed by 'git add ./*' if len(dot_items) > 0: execute_command('git add ' + ' '.join(dot_items), cwd=directory) # Remove any straggling untracked files execute_command('git clean -dXf', cwd=directory) # Only if we have local changes commit # (not true if the upstream didn't change any files) cmd = 'git commit ' if not has_changes(directory): cmd += '--allow-empty ' cmd += '-m "Rebase from \'' + upstream_branch + "'" if not has_changes(directory): cmd += " (no changes)" cmd += '"' execute_command(cmd, cwd=directory) finally: # Clean up if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) def git_rebase(upstream_branch, directory=None): """ Not currently used, because the more explicit merge must be used when trimming is done. For example: Upstream @ 1.0: foo/ foo.rst bar/ bar.rst release/foo @ 1.0: foo.rst release/bar @ 1.0: bar.rst Then... @ upstream update Upstream @ 1.1: LICENSE.txt foo/ foo.rst bar/ bar.rst release/foo @ 1.1: LICENSE.txt foo.rst release/bar @ 1.1: LICENSE.txt bar.rst The LICENSE.txt survives because the original trim patches do not contain a removal of LICENSE.txt in the diff (the trim patches are explicit) """ raise NotImplementedError('bloom.commands.git.patch.rebase_cmd.git_rebase') @log_prefix('[git-bloom-patch rebase]: ') def rebase_patches(without_git_rebase=True, directory=None): # Ensure a clean/valid working environment ensure_clean_working_env(git_status=True, directory=directory) # Make sure we need to actually call this # Get the current branch current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) # Get the patches branch patches_branch = 'patches/' + current_branch # Get the current patches.conf config = get_patch_config(patches_branch, directory=directory) # Execute the rebase if without_git_rebase: non_git_rebase(config['parent'], directory=directory) else: git_rebase(config['parent'], directory=directory) # Update the patches information # Get the latest configs config = get_patch_config(patches_branch, directory) # Set the base to the current hash (before patches) current_branch_ = get_current_branch(directory) debug('Current branch: ' + current_branch_ or 'could not determine branch') config['base'] = get_commit_hash(current_branch_, directory) debug('New current commit hash after rebase: ' + config['base']) # Set the new upstream hash to the previous upstream hash config['previous'] = get_commit_hash(config['parent'], directory) debug('New parent commit hash after rebase: ' + config['previous']) # Clear the trimbase (it needs to be reapplied) config['trimbase'] = '' # Write the new configs set_patch_config(patches_branch, config, directory) def add_parser(subparsers): parser = subparsers.add_parser( 'rebase', description="""\ This command sets the contents of the current branch to the contents of the parent (upstream) branch. It does this by replacing the current branch contents with parent contents: - delete current branch contents, copy over upstream branch contents - clear trim flag (so it can be reapplied) - update patches.conf {0}\ WARNING: make sure to export patches and commit local changes before rebasing\ {1} """.format(ansi('yellowf'), ansi('reset')), formatter_class=argparse.RawTextHelpFormatter ) parser.set_defaults(func=main) add_global_arguments(parser) return parser def main(args): handle_global_arguments(args) return rebase_patches() bloom-0.10.7/bloom/commands/git/patch/remove_cmd.py000077500000000000000000000054271403641720100222050ustar00rootroot00000000000000from __future__ import print_function from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config from bloom.git import branch_exists from bloom.git import checkout from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import track_branches from bloom.logging import log_prefix from bloom.logging import error from bloom.logging import debug from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import handle_global_arguments @log_prefix('[git-bloom-patch remove]: ') def remove_patches(directory=None): # Get the current branch current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) # Ensure the current branch is valid if current_branch is None: error("Could not determine current branch, are you in a git repo?", exit=True) # Construct the patches branch patches_branch = 'patches/' + current_branch try: # See if the patches branch exists if branch_exists(patches_branch, False, directory=directory): if not branch_exists(patches_branch, True, directory=directory): track_branches(patches_branch, directory) else: error("No patches branch (" + patches_branch + ") found, cannot " "remove patches.", exit=True) # Get the parent branch from the patches branch config = get_patch_config(patches_branch, directory=directory) parent, spec = config['parent'], config['base'] if None in [parent, spec]: error("Could not retrieve patches info.", exit=True) debug("Removing patches from " + current_branch + " back to base " "commit " + spec) # Reset this branch using git revert --no-edit spec current_commit = get_commit_hash(current_branch, directory) command_spec = spec + '..' + current_commit execute_command( 'git revert --no-edit -Xtheirs ' + command_spec, cwd=directory ) # Update the base config['base'] = get_commit_hash(current_branch, directory) set_patch_config(patches_branch, config, directory=directory) finally: if current_branch: checkout(current_branch, directory=directory) def add_parser(subparsers): parser = subparsers.add_parser( 'remove', description="Removes any applied patches from the working branch, " "including any un-exported patches, so use with caution." ) parser.set_defaults(func=main) add_global_arguments(parser) return parser def main(args): handle_global_arguments(args) return remove_patches() bloom-0.10.7/bloom/commands/git/patch/trim_cmd.py000077500000000000000000000200041403641720100216470ustar00rootroot00000000000000from __future__ import print_function import sys import os import tempfile import shutil from bloom.git import branch_exists from bloom.git import checkout from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import get_root from bloom.git import track_branches from bloom.logging import debug from bloom.logging import log_prefix from bloom.logging import error from bloom.logging import warning from bloom.util import add_global_arguments from bloom.util import execute_command from bloom.util import handle_global_arguments from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config def _set_trim_sub_dir(sub_dir, force, config, directory): debug("_set_trim_sub_dir(" + str(sub_dir) + ", " + str(force) + ", " + str(config) + ", " + str(directory) + ")") if sub_dir is not None: if config['trim'] != '' and config['trim'] != sub_dir: warning("You are trying to set the trim sub directory to " + sub_dir + ", but it is already set to " + config['trim'] + ".") if not force: warning("Changing the sud directory is not advised. " "If you are sure you want to do this, use " "'--force'") return None else: warning("Forcing the change of the sub directory.") # Make the sub_dir absolute git_root = get_root(directory) sub_dir_abs = os.path.join(git_root, sub_dir) # Make sure it is a directory if not os.path.isdir(sub_dir_abs): error("The given sub directory, (" + sub_dir + ") does not " "exist in the git repository at " + git_root) return None # Set the trim sub directory config['trim'] = sub_dir return config def _undo(config, directory): debug("_undo(" + str(config) + ", " + str(directory) + ")") # TODO: handle repo with changes # TODO: handle repo with patches applied if config['trimbase'] == '': debug("Branch has not been trimmed previously, undo not required.") return None # Reset with git-revert current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) cmt = get_commit_hash(current_branch, directory) cmd = 'git revert --no-edit -Xtheirs ' + config['trimbase'] + '..' + cmt execute_command(cmd, cwd=directory) # Unset the trimbase config['trimbase'] = '' return config def _trim(config, force, directory): debug("_trim(" + str(config) + ", " + str(force) + ", " + str(directory) + ")") if config['trimbase'] != '': warning("It looks like the trim operation has already been done, " "nested trimming is not supported.") if force: warning("Proceeding anyways because of '--force'") else: warning("If you would like to continue anyways use '--force'") return None current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) config['trimbase'] = get_commit_hash(current_branch) tmp_dir = tempfile.mkdtemp() try: # Buckup trim sub directory git_root = get_root() sub_dir = os.path.join(git_root, config['trim']) storage = os.path.join(tmp_dir, config['trim']) shutil.copytree(sub_dir, storage) # Clear out any untracked files execute_command('git clean -fdx', cwd=directory) # Collect al files (excluding .git) items = [] for item in os.listdir(git_root): if item in ['.git', '..', '.']: continue items.append(item) # Remove and .* files missed by 'git rm -rf *' if len(items) > 0: execute_command('git rm -rf ' + ' '.join(["'{}'".format(i) for i in items if i]), cwd=directory) # Copy the sub directory back for item in os.listdir(storage): src = os.path.join(storage, item) dst = os.path.join(git_root, item) if os.path.isdir(src): shutil.copytree(src, dst) else: shutil.copy(src, dst) # Stage execute_command('git add ./*', cwd=directory) # Collect .* files dot_items = [] for item in os.listdir(git_root): if item in ['.git', '..', '.']: continue if item.startswith('.'): dot_items.append(item) # Add any .* files missed by 'git add ./*' if len(dot_items) > 0: execute_command('git add ' + ' '.join(dot_items), cwd=directory) # Remove any straggling untracked files execute_command('git clean -dXf', cwd=directory) # Commit cmd = 'git commit -m "Trimmed the branch to only the ' + \ config['trim'] + ' sub directory"' execute_command(cmd, cwd=directory) # Update the patch base to be this commit current_branch = get_current_branch(directory) if current_branch is None: error("Could not determine current branch.", exit=True) config['base'] = get_commit_hash(current_branch) finally: if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) return config @log_prefix('[git-bloom-patch trim]: ') def trim(sub_dir=None, force=False, undo=False, directory=None): # Get the current branch current_branch = get_current_branch(directory) # Ensure the current branch is valid if current_branch is None: error("Could not determine current branch, are you in a git repo?", exit=True) # Construct the patches branch patches_branch = 'patches/' + current_branch try: # See if the patches branch exists if branch_exists(patches_branch, False, directory=directory): if not branch_exists(patches_branch, True, directory=directory): track_branches(patches_branch, directory) else: error("No patches branch (" + patches_branch + ") found, cannot " "perform trim.", exit=True) # Get the parent branch from the patches branch config = get_patch_config(patches_branch, directory=directory) if config is None: error("Could not retrieve patches info.", exit=True) # If sub_dir is set, try to set it new_config = _set_trim_sub_dir(sub_dir, force, config, directory) if new_config is None: sys.exit('Could not perform trim') # Perform trime or undo if undo: new_config = _undo(new_config, directory) if new_config is None: return -1 # Indicates that nothing was done else: new_config = _trim(new_config, force, directory) if new_config is None: sys.exit('Could not perform trim') # Commit the new config set_patch_config(patches_branch, new_config, directory) finally: if current_branch: checkout(current_branch, directory=directory) def add_parser(subparsers): parser = subparsers.add_parser( 'trim', description="""\ Moves a given sub directory into the root of the git repository. If you call trim on a patched branch (even --undo), bad things will happen...\ """ ) parser.set_defaults(func=main) add = parser.add_argument add('--sub-directory', '-s', metavar='SUB_DIRECTORY', help="the sub directory to move the root of the repository", default=None) add('--force', '-f', help="force the change of the SUB_DIRECTORY if set", action='store_true', default=False) add('--undo', '-u', help="reverses the the trim using 'git reset --hard'", action='store_true', default=False) add_global_arguments(parser) return parser def main(args): handle_global_arguments(args) return trim(args.sub_directory, args.force, args.undo) bloom-0.10.7/bloom/commands/git/release.py000066400000000000000000000372651403641720100204100ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import atexit import os import shutil import subprocess import sys import tempfile import traceback from bloom.config import DEFAULT_TEMPLATE from bloom.config import get_tracks_dict_raw from bloom.config import template_str from bloom.config import verify_track from bloom.config import write_tracks_dict_raw from bloom.git import ensure_clean_working_env from bloom.git import ensure_git_root from bloom.git import get_current_branch from bloom.git import get_root from bloom.git import GitClone from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import get_error_prefix from bloom.logging import info from bloom.logging import sanitize from bloom.logging import warning from bloom.packages import get_package_data import bloom.util from bloom.util import add_global_arguments from bloom.util import change_directory from bloom.util import code from bloom.util import disable_git_clone from bloom.util import handle_global_arguments from bloom.util import maybe_continue from bloom.util import quiet_git_clone_warning from bloom.util import safe_input try: from vcstools.vcs_abstraction import get_vcs_client except ImportError: debug(traceback.format_exc()) error("vcstools was not detected, please install it.", file=sys.stderr, exit=True) upstream_repos = {} _error = get_error_prefix() def get_upstream_repo(uri, vcs_type): global upstream_repos if uri not in upstream_repos: temp_dir = tempfile.mkdtemp() upstream_dir = os.path.join(temp_dir, 'upstream') upstream_repos[uri] = (temp_dir, get_vcs_client(vcs_type, upstream_dir)) return upstream_repos[uri][1] @atexit.register def clean_up_repositories(): global upstream_repos for uri in upstream_repos: path = upstream_repos[uri][0] if os.path.exists(path): shutil.rmtree(path) # def find_version_from_upstream_github(vcs_uri, devel_branch=None): # # TODO: Implement this # info(' raw.github.com checking is not implemented yet.') # return None def get_upstream_meta(upstream_dir, ros_distro): meta = None directory = os.getcwd() with change_directory(upstream_dir): if get_root() is not None: # If in a git repo current_branch = get_current_branch() else: current_branch = None name, version, packages = get_package_data(current_branch, quiet=False, release_directory=directory) meta = { 'name': name, 'version': version, 'type': 'package.xml' } return meta def find_version_from_upstream(vcs_uri, vcs_type, devel_branch=None, ros_distro='indigo'): # Check for github.com # if vcs_uri.startswith('http') and 'github.com' in vcs_uri: # info("Detected github.com repository, checking for package.xml " # "in root of devel branch using raw.github.com...") # version = find_version_from_upstream_github(vcs_uri, devel_branch) # if version: # return version, None # warning(" Failed to find the version using raw.github.com.") # Try to clone the upstream repository info("Checking upstream devel branch '{0}' for package.xml(s)".format(devel_branch or '')) upstream_repo = get_upstream_repo(vcs_uri, vcs_type) if not upstream_repo.checkout(vcs_uri, devel_branch or ''): error("Failed to checkout to the upstream branch " "'{0}' in the repository from '{1}'" .format(devel_branch or '', vcs_uri), exit=True) meta = get_upstream_meta(upstream_repo.get_path(), ros_distro) if not meta: error("Failed to find any package.xml(s) in the " "upstream devel branch '{0}' in the repository from '{1}'" .format(devel_branch or '', vcs_uri)) info("Detected version '{0}' from package(s): {1}" .format(meta['version'], meta['name'])) return meta['version'], upstream_repo def process_track_settings(track_dict, release_inc_override, interactive=True): settings = {} settings['name'] = track_dict['name'] vcs_uri = track_dict['vcs_uri'] # Is the vcs_uri set? if vcs_uri is None or vcs_uri.lower() == ':{none}': error("The '{0}' must be set to something other than None." .format(DEFAULT_TEMPLATE['vcs_uri'].name), exit=True) # Is the vcs_type set and valid? vcs_type = track_dict['vcs_type'] vcs_type_prompt = DEFAULT_TEMPLATE['vcs_type'] if vcs_type is None or vcs_type.lower() not in vcs_type_prompt.values: error("The '{0}' cannot be '{1}', valid values are: {2}" .format(vcs_type_prompt.name, vcs_type, vcs_type_prompt.values), exit=True) settings['vcs_type'] = vcs_type # Is the version set to auto? version = track_dict['version'] track_dict['ros_distro'] = str(track_dict['ros_distro'].lower()) repo = None if version.lower() == ':{auto}': # Is the vcs_type either hg, git, or svn? if vcs_type not in ['git', 'hg', 'svn']: error("Auto detection of version is not supported for '{0}'" .format(vcs_type), exit=True) devel_branch = track_dict['devel_branch'] try: string_types = [str, unicode] except NameError: string_types = [str] if type(devel_branch) in string_types \ and devel_branch.lower() == ':{none}': devel_branch = None version, repo = find_version_from_upstream(vcs_uri, vcs_type, devel_branch, track_dict['ros_distro']) if version is None: warning("Could not determine the version automatically.") if version is None or version == ':{ask}': if interactive: ret = safe_input('What version are you releasing ' '(version should normally be MAJOR.MINOR.PATCH)? ') if not ret: error("You must specify a version to continue.", exit=True) version = ret else: error("Interactivity is disabled but version is set to :{ask}", exit=True) settings['version'] = version vcs_uri = vcs_uri.replace(':{version}', version) settings['vcs_local_uri'] = repo.get_path() if repo else vcs_uri # Now that we have a version, template the vcs_uri if needed if ':{version}' in vcs_uri: vcs_uri = vcs_uri.replace(':{version}', version) settings['vcs_uri'] = vcs_uri # Is the release tag set to ask release_tag = track_dict['release_tag'] release_tag_prompt = DEFAULT_TEMPLATE['release_tag'] if release_tag is not None and release_tag == ':{ask}': if interactive: ret = safe_input('What upstream tag should bloom import from? ') if not ret: error("You must specify a release tag.", exit=True) release_tag = ret else: error("Interactivity is disabled but release_tag is set to :{ask}", exit=True) elif release_tag is None or release_tag.lower() == ':{none}': if vcs_type not in ['svn', 'tar']: error("'{0}' can not be None unless '{1}' is either 'svn' or 'tar'" .format(release_tag_prompt.name, vcs_type_prompt.name)) release_tag = ':{none}' else: release_tag = release_tag.replace(':{version}', version) settings['release_tag'] = release_tag # Transfer other settings settings['devel_branch'] = track_dict['devel_branch'] settings['patches'] = track_dict['patches'] or '' settings['ros_distro'] = track_dict['ros_distro'] # Release increment if 'last_version' in track_dict and track_dict['last_version'] != version: next_release_inc = str(1) else: next_release_inc = str(int(track_dict['release_inc']) + 1) settings['release_inc'] = release_inc_override or next_release_inc return settings def find_full_path(cmd): for path in os.environ.get('PATH', '').split(os.pathsep): full_path = os.path.join(path, cmd) if os.path.isfile(full_path): return full_path raise OSError("[Errno 2] No such file or directory") def execute_track(track, track_dict, release_inc, pretend=True, debug=False, fast=False, interactive=True): info("Processing release track settings for '{0}'".format(track)) settings = process_track_settings(track_dict, release_inc, interactive=interactive) # setup extra settings archive_dir_path = tempfile.mkdtemp() settings['archive_dir_path'] = archive_dir_path if settings['release_tag'] != ':{none}': archive_file = '{name}-{release_tag}.tar.gz'.format(**settings) else: archive_file = '{name}.tar.gz'.format(**settings) settings['archive_path'] = os.path.join(archive_dir_path, archive_file) # execute actions info("", use_prefix=False) info("Executing release track '{0}'".format(track)) for action in track_dict['actions']: if 'bloom-export-upstream' in action and settings['vcs_type'] == 'tar': warning("Explicitly skipping bloom-export-upstream for tar.") settings['archive_path'] = settings['vcs_uri'] continue templated_action = template_str(action, settings) info(fmt("@{bf}@!==> @|@!" + sanitize(str(templated_action)))) if pretend: continue stdout = None stderr = None if bloom.util._quiet: stdout = subprocess.PIPE stderr = subprocess.STDOUT if debug and 'DEBUG' not in os.environ: os.environ['DEBUG'] = '1' if fast and 'BLOOM_UNSAFE' not in os.environ: os.environ['BLOOM_UNSAFE'] = '1' templated_action = templated_action.split() templated_action[0] = find_full_path(templated_action[0]) p = subprocess.Popen(templated_action, stdout=stdout, stderr=stderr, shell=False, env=os.environ.copy()) out, err = p.communicate() if bloom.util._quiet: info(out, use_prefix=False) ret = p.returncode if ret > 0: if 'bloom-generate' in templated_action[0] and ret == code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO: error(fmt(_error + "The following generator action reported that it is missing one or more")) error(fmt(" @|rosdep keys, but that the key exists in other platforms:")) error(fmt("@|'@!{0}'@|").format(templated_action)) info('', use_prefix=False) error(fmt("@|If you are @!@_@{rf}absolutely@| sure that this key is unavailable for the platform in")) error(fmt("@|question, the generator can be skipped and you can proceed with the release.")) if interactive and maybe_continue('n', 'Skip generator action and continue with release'): info("\nAction skipped, continuing with release.\n") continue info('', use_prefix=False) error(fmt(_error + "Error running command '@!{0}'@|") .format(templated_action), exit=True) info('', use_prefix=False) if not pretend: # Update the release_inc tracks_dict = get_tracks_dict_raw() tracks_dict['tracks'][track]['release_inc'] = settings['release_inc'] tracks_dict['tracks'][track]['last_version'] = settings['version'] # if release tag is set to ask and a custom value is used if settings['version'] != settings['release_tag']: tracks_dict['tracks'][track]['last_release'] = settings['release_tag'] write_tracks_dict_raw(tracks_dict, 'Updating release inc to: ' + str(settings['release_inc'])) def get_argument_parser(tracks): parser = argparse.ArgumentParser(description="Executes a release track.") add = parser.add_argument add('track', choices=tracks, help="release track to execute") add('--release-increment', '-i', help="overrides the automatic release increment number") add('--pretend', '-p', action="store_true", default=False, help="does everything but actually run the commands") add('--non-interactive', '-y', action="store_false", default=True, help="runs without user interaction", dest='interactive') return parser def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() # Check that the current directory is a serviceable git/bloom repo ensure_clean_working_env() ensure_git_root() # Get tracks tracks_dict = get_tracks_dict_raw() if not tracks_dict['tracks']: error("No tracks configured, first create a track with " "'git-bloom-config new '", exit=True) # Do argparse stuff parser = get_argument_parser([str(t) for t in tracks_dict['tracks']]) parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) os.environ['BLOOM_TRACK'] = args.track verify_track(args.track, tracks_dict['tracks'][args.track]) git_clone = GitClone() with git_clone: quiet_git_clone_warning(True) disable_git_clone(True) execute_track(args.track, tracks_dict['tracks'][args.track], args.release_increment, args.pretend, args.debug, args.unsafe, interactive=args.interactive) disable_git_clone(False) quiet_git_clone_warning(False) git_clone.commit() # Notify the user of success and next action suggestions info('\n\n', use_prefix=False) warning("Tip: Check to ensure that the debian tags created have the same " "version as the upstream version you are releasing.") info(fmt("@{gf}@!Everything went as expected, " "you should check that the new tags match your expectations, and " "then push to the release repo with:@|")) info(fmt(" git push --all && git push --tags " "@{kf}@!# You might have to add --force to the second command if you " "are over-writing existing tags")) bloom-0.10.7/bloom/commands/release.py000066400000000000000000001546211403641720100176210ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2014, Open Source Robotics Foundation, Inc. # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from __future__ import unicode_literals import argparse import atexit import datetime import difflib import os import pkg_resources import platform import shutil import subprocess import sys import tempfile import traceback import webbrowser import yaml from pkg_resources import parse_version # python2/3 compatibility try: from urllib.error import HTTPError, URLError from urllib.parse import urlparse from urllib.request import Request, urlopen except ImportError: from urllib2 import HTTPError, Request, URLError, urlopen from urlparse import urlparse import bloom from bloom.config import get_tracks_dict_raw from bloom.config import upconvert_bloom_to_config_branch from bloom.config import write_tracks_dict_raw from bloom.git import branch_exists from bloom.git import checkout from bloom.git import get_branches from bloom.git import get_current_branch from bloom.git import inbranch from bloom.git import ls_tree from bloom.github import GithubException from bloom.github import get_gh_info from bloom.github import get_github_interface from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import get_error_prefix from bloom.logging import get_success_prefix from bloom.logging import info from bloom.logging import sanitize from bloom.logging import warning from bloom.packages import get_package_data from bloom.packages import get_ignored_packages from bloom.rosdistro_api import get_distribution_file from bloom.rosdistro_api import get_index from bloom.rosdistro_api import get_most_recent from bloom.rosdistro_api import get_rosdistro_index_commit from bloom.rosdistro_api import get_rosdistro_index_original_branch from bloom.summary import commit_summary from bloom.summary import get_summary_file from bloom.util import add_global_arguments from bloom.util import change_directory from bloom.util import disable_git_clone from bloom.util import get_rfc_2822_date from bloom.util import handle_global_arguments from bloom.util import load_url_to_file_handle from bloom.util import maybe_continue from bloom.util import quiet_git_clone_warning from bloom.util import safe_input from bloom.util import temporary_directory from bloom.util import to_unicode try: import vcstools except ImportError: debug(traceback.format_exc()) error("vcstools was not detected, please install it.", file=sys.stderr, exit=True) import vcstools.__version__ from vcstools.vcs_abstraction import get_vcs_client from rosdistro import DistributionFile from rosdistro import get_distribution_files from rosdistro import get_index_url from rosdistro.writer import yaml_from_distribution_file try: import rosdep2 except ImportError: debug(traceback.format_exc()) error("rosdep was not detected, please install it.", file=sys.stderr, exit=True) try: import catkin_pkg except ImportError: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", file=sys.stderr, exit=True) from catkin_pkg.changelog import get_changelog_from_path _repositories = {} _success = get_success_prefix() _error = get_error_prefix() _user_provided_release_url = None @atexit.register def exit_cleanup(): global _repositories for repo in _repositories.values(): repo_path = repo.get_path() if os.path.exists(repo_path): shutil.rmtree(repo_path) _rosdistro_distribution_file_urls = {} def get_distribution_file_url(distro): global _rosdistro_distribution_file_urls if distro not in _rosdistro_distribution_file_urls: index = get_index() if distro not in index.distributions: error("'{0}' distro is not in the index file.".format(distro), exit=True) distro_file = index.distributions[distro] if 'distribution' not in distro_file: error("'{0}' distro does not have a distribution file.".format(distro), exit=True) if isinstance(distro_file['distribution'], list): _rosdistro_distribution_file_urls[distro] = distro_file['distribution'][-1] else: _rosdistro_distribution_file_urls[distro] = distro_file['distribution'] return _rosdistro_distribution_file_urls[distro] def validate_github_url(url, url_type): if 'github.com' not in url: return True valid_url = True if not url.endswith('.git') and not url.endswith('.git/'): valid_url = False warning("The {0} repository url you provided does not end in `.git`." .format(url_type)) if not url.startswith('https://'): valid_url = False warning("The {0} repository url you provided is not a `https://` address." .format(url_type)) if not valid_url: if maybe_continue(msg="Would you like to enter the address again"): return False else: warning("Very well, the address '{0}' will be used as is.".format(url)) return True # url is OK return True def infer_release_repo_from_env(repository): """ Generate a release repo url from a hint variable. If the environment var BLOOM_RELEASE_REPO_BASE exists, and BLOOM_RELEASE_REPO_BASE + repository + '-release.git' exists online, then this function will return the newly composed url """ base = os.environ.get('BLOOM_RELEASE_REPO_BASE', None) if base is None: return None url = base + repository + '-release.git' try: urlopen(Request(url)) except URLError: return None except HTTPError: return None return url def get_repo_uri(repository, distro): url = None # Fetch the distro file distribution_file = get_distribution_file(distro) if repository in distribution_file.repositories and \ distribution_file.repositories[repository].release_repository is not None: url = distribution_file.repositories[repository].release_repository.url else: error("Specified repository '{0}' is not in the distribution file located at '{1}'" .format(repository, get_distribution_file_url(distro))) matches = difflib.get_close_matches(repository, distribution_file.repositories) if matches: info(fmt("@{yf}Did you mean one of these: '" + "', '".join([m for m in matches]) + "'?")) if url is None: url = infer_release_repo_from_env(repository) if url is None: info("Could not determine release repository url for repository '{0}' of distro '{1}'" .format(repository, distro)) info("You can continue the release process by manually specifying the location of the RELEASE repository.") info("To be clear this is the url of the RELEASE repository not the upstream repository.") info("For release repositories on GitHub, you should provide the `https://` url which should end in `.git`.") info("Here is the url for a typical release repository on GitHub: https://github.com/ros-gbp/rviz-release.git") # Calculate a reasonable default from the list of previous distros info(fmt("@{gf}@!==> @|") + "Looking for a release of this repository in a different distribution...") default_distro, default_release = get_most_recent('release', repository, distro) default_release_repo_url = default_release.url if default_release else "press enter to abort" if default_distro is not None: warning("A different distribution, '{0}', released this repository.".format(default_distro)) else: warning("No reasonable default release repository url could be determined from previous releases.") while True: url = None try: url = safe_input('Release repository url [{0}]: '.format(default_release_repo_url)) except (KeyboardInterrupt, EOFError): info('', use_prefix=False) error("User exited at prompt.", exit=True) if not url: if default_distro is None: url = None error("No release repository url given, aborting.", exit=True) if url is not None: url = default_release_repo_url if url is None: break # If github.com address, validate it # If not, validate_github_url will print some messages about why if not validate_github_url(url, 'release'): continue break global _user_provided_release_url _user_provided_release_url = url return url def get_release_repo(repository, distro, override_url): global _repositories if override_url is not None: warning("Overriding the release repository url, using '{0}'".format(override_url)) url = override_url else: url = get_repo_uri(repository, distro) if repository not in _repositories.values(): temp_dir = tempfile.mkdtemp() _repositories[repository] = get_vcs_client('git', temp_dir) info(fmt("@{gf}@!==> @|") + "Fetching '{0}' repository from '{1}'".format(repository, url)) _repositories[repository].checkout(url, 'master') return _repositories[repository] def check_for_bloom_conf(repository): bloom_ls = ls_tree('bloom') if bloom_ls is None: return False bloom_files = [f for f, t in bloom_ls.items() if t == 'file'] return 'bloom.conf' in bloom_files def list_tracks(repository, distro, override_release_repository_url): release_repo = get_release_repo(repository, distro, override_release_repository_url) tracks_dict = None with change_directory(release_repo.get_path()): upconvert_bloom_to_config_branch() if check_for_bloom_conf(repository): info("No tracks, but old style bloom.conf available for conversion") else: tracks_dict = get_tracks_dict_raw() if tracks_dict and tracks_dict['tracks'].keys(): info("Available tracks: " + str(tracks_dict['tracks'].keys())) else: error("Release repository has no tracks nor an old style bloom.conf file.", exit=True) return tracks_dict['tracks'].keys() if tracks_dict else None def get_relative_distribution_file_path(distro): distribution_file_url = urlparse(get_distribution_file_url(distro)) index_file_url = urlparse(get_index_url()) return os.path.relpath(distribution_file_url.path, os.path.commonprefix([index_file_url.path, distribution_file_url.path])) def generate_release_tag(distro): tag = ('release/%s/{package}/{version}' % distro) if sys.version_info[0] < 3: tag == tag.encode('utf-8') return tag def generate_ros_distro_diff(track, repository, distro, override_release_repository_url): def convert_unicode_dict_to_str(d): for key, value in d.items(): if type(key) == unicode: del d[key] key = key.encode('utf-8') if type(value) == unicode: value = value.encode('utf-8') if type(value) == dict: convert_unicode_dict_to_str(value) d[key] = value global _user_provided_release_url distribution_dict = get_distribution_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") # Get version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] version = '{0}-{1}'.format(last_version, release_inc) # Create a repository if there isn't already one if repository not in distribution_dict['repositories']: distribution_dict['repositories'][repository] = {} # Create a release entry if there isn't already one if 'release' not in distribution_dict['repositories'][repository]: distribution_dict['repositories'][repository]['release'] = { 'url': override_release_repository_url or _user_provided_release_url } # Update the repository repo = distribution_dict['repositories'][repository]['release'] # Consider the override if override_release_repository_url is not None: repo['url'] = override_release_repository_url if 'tags' not in repo: repo['tags'] = {} repo['tags']['release'] = generate_release_tag(distro) repo['version'] = version if 'last_release' in track_dict: repo['upstream_tag'] = track_dict['last_release'] if 'packages' not in repo: repo['packages'] = [] for path, pkg in packages.items(): if pkg.name not in repo['packages']: repo['packages'].append(pkg.name) # Remove any missing packages packages_being_released = [p.name for p in packages.values()] for pkg_name in list(repo['packages']): if pkg_name not in packages_being_released: repo['packages'].remove(pkg_name) repo['packages'].sort() if sys.version_info[0] < 3: convert_unicode_dict_to_str(repo) def get_repository_info_from_user(url_type, defaults=None): data = {} defaults = defaults or {} while True: info("VCS Type must be one of git, svn, hg, or bzr.") default = defaults.get('type', None) insert = '' if default is None else ' [{0}]'.format(default) vcs_type = safe_input('VCS type{0}: '.format(insert)) if not vcs_type: vcs_type = default if vcs_type in ['git', 'svn', 'hg', 'bzr']: break error("'{0}' is not a valid vcs type.".format(vcs_type)) if not maybe_continue(msg='Try again'): return {} data['type'] = vcs_type while True: default = defaults.get('url', None) insert = '' if default is None else ' [{0}]'.format(default) url = safe_input('VCS url{0}: '.format(insert)) if not url: url = default if url: if not validate_github_url(url, url_type): # User wants to try again continue break error("Nothing entered for url.") if not maybe_continue(msg='Try again'): return {} data['url'] = url while True: info("VCS version must be a branch, tag, or commit, e.g. master or 0.1.0") default = defaults.get('version', None) insert = '' if default is None else ' [{0}]'.format(default) version = safe_input('VCS version{0}: '.format(insert)) if not version: version = default if version: break error("Nothing entered for version.") if not maybe_continue(msg='Try again'): return {} data['version'] = version return data # Ask for doc entry if 'BLOOM_DONT_ASK_FOR_DOCS' not in os.environ: docs = distribution_dict['repositories'][repository].get('doc', {}) if not docs and maybe_continue(msg='Would you like to add documentation information for this repository?'): defaults = None info(fmt("@{gf}@!==> @|") + "Looking for a doc entry for this repository in a different distribution...") default_distro, default_doc = get_most_recent('doc', repository, distro) if default_distro is None: warning("No existing doc entries found for use as defaults.") else: warning("Using defaults from the doc entry of distribution '{0}'.".format(default_distro)) if default_doc is not None: defaults = { 'type': default_doc.type or None, 'url': default_doc.url or None, 'version': default_doc.version or None, } info("Please enter your repository information for the doc generation job.") info("This information should point to the repository from which documentation should be generated.") docs = get_repository_info_from_user('doc', defaults) distribution_dict['repositories'][repository]['doc'] = docs # Ask for source entry if 'BLOOM_DONT_ASK_FOR_SOURCE' not in os.environ: source = distribution_dict['repositories'][repository].get('source', {}) if not source and maybe_continue(msg='Would you like to add source information for this repository?'): defaults = None info(fmt("@{gf}@!==> @|") + "Looking for a source entry for this repository in a different distribution...") default_distro, default_source = get_most_recent('source', repository, distro) if default_distro is None: warning("No existing source entries found for use as defaults.") else: warning("Using defaults from the source entry of distribution '{0}'.".format(default_distro)) if default_source is not None: defaults = { 'type': default_source.type or None, 'url': default_source.url or None, 'version': default_source.version or None, } info("Please enter information which points to the active development branch for this repository.") info("This information is used to run continuous integration jobs and for developers to checkout from.") source = get_repository_info_from_user('source', defaults) if validate_github_url(source['url'], 'source'): info("Since you are on github we can add a job to run your tests on each pull request." "If you would like to turn this on please see " "http://wiki.ros.org/buildfarm/Pull%20request%20testing for more information. " "There is more setup required to setup the hooks correctly. ") if maybe_continue(msg='Would you like to turn on pull request testing?', default='n'): source['test_pull_requests'] = 'true' distribution_dict['repositories'][repository]['source'] = source # Ask for maintainership information if 'BLOOM_DONT_ASK_FOR_MAINTENANCE_STATUS' not in os.environ: status = distribution_dict['repositories'][repository].get('status', None) description = distribution_dict['repositories'][repository].get('status_description', None) if status is None and maybe_continue(msg='Would you like to add a maintenance status for this repository?'): info("Please enter a maintenance status.") info("Valid maintenance statuses:") info("- developed: active development is in progress") info("- maintained: no new development, but bug fixes and pull requests are addressed") info("- unmaintained: looking for new maintainer, bug fixes and pull requests will not be addressed") info("- end-of-life: should not be used, will disappear at some point") while True: status = safe_input('Status: ') if status in ['developed', 'maintained', 'unmaintained', 'end-of-life']: break error("'{0}' is not a valid status.".format(status)) if not maybe_continue(msg='Try again'): status = None break if status is not None: info("You can also enter a status description.") info("This is usually reserved for giving a reason when a status is 'end-of-life'.") if description is not None: info("Current status description: {0}".format(description)) description_in = safe_input('Status Description [press Enter for no change]: ') if description_in: description = description_in if status is not None: distribution_dict['repositories'][repository]['status'] = status if description is not None: distribution_dict['repositories'][repository]['status_description'] = description # Do the diff distro_file_name = get_relative_distribution_file_path(distro) updated_distribution_file = DistributionFile(distro, distribution_dict) distro_dump = yaml_from_distribution_file(updated_distribution_file) distro_file_raw = load_url_to_file_handle(get_distribution_file_url(distro)).read().decode('utf-8') if distro_file_raw != distro_dump: # Calculate the diff udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + sanitize(line)) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + sanitize(line)) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + sanitize(line)) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') # Assert that only this repository is being changed distro_file_yaml = yaml.safe_load(distro_file_raw) distro_yaml = yaml.safe_load(distro_dump) if 'repositories' in distro_file_yaml: distro_file_repos = distro_file_yaml['repositories'] for repo in distro_yaml['repositories']: if repo == repository: continue if repo not in distro_file_repos or distro_file_repos[repo] != distro_yaml['repositories'][repo]: error("This generated pull request modifies a repository entry other than the one being released.") error("This likely occurred because the upstream rosdistro changed during this release.") error("This pull request will abort, please re-run this command with the -p option to try again.", exit=True) # Write the diff out to file with open(udiff_file, 'w+') as f: f.write(udiff_raw) # Return the diff return updated_distribution_file else: warning("This release resulted in no changes to the ROS distro file...") return None def get_repo_info(distro_url): gh_info = get_gh_info(distro_url) if gh_info: return gh_info def get_changelog_summary(release_tag): summary = u"" packages = dict([(p.name, p) for p in get_packages().values()]) for package_name in sorted(packages.keys()): package = packages[package_name] release_branch = '/'.join(release_tag.split('/')[:-1]).format(package=package.name) if not branch_exists(release_branch): continue with inbranch(release_branch): changelog = get_changelog_from_path(os.getcwd()) if changelog is None: continue for version, date, changes in changelog.foreach_version(): if version == package.version: msgs = [] for change in changes: msgs.extend([i for i in to_unicode(change).splitlines()]) msg = '\n'.join(msgs) summary += u""" ## {package.name} """.format(**locals()) if msg: summary += u""" ``` {msg} ``` """.format(**locals()) else: summary += u""" - No changes """ return summary def open_pull_request(track, repository, distro, interactive, override_release_repository_url): # Get the diff distribution_file = get_distribution_file(distro) if repository in distribution_file.repositories and \ distribution_file.repositories[repository].release_repository is not None: orig_version = distribution_file.repositories[repository].release_repository.version else: orig_version = None updated_distribution_file = generate_ros_distro_diff(track, repository, distro, override_release_repository_url) if updated_distribution_file is None: # There were no changes, no pull request required return None version = updated_distribution_file.repositories[repository].release_repository.version updated_distro_file_yaml = yaml_from_distribution_file(updated_distribution_file) # Determine where the distro file is hosted... distro_url = get_distribution_file_url(distro) base_info = get_repo_info(distro_url) if not base_info: warning("Automated pull request only available via github.com") return # If we did replace the branch in the url with a commit, restore that now rosdistro_index_original_branch = get_rosdistro_index_original_branch() if rosdistro_index_original_branch is not None: base_info['branch'] = rosdistro_index_original_branch # Create content for PR title = "{0}: {1} in '{2}' [bloom]".format(repository, version, base_info['path']) track_dict = get_tracks_dict_raw()['tracks'][track] body = u"""\ Increasing version of package(s) in repository `{repository}` to `{version}`: - upstream repository: {upstream_repo} - release repository: {release_repo} - distro file: `{distro_file}` - bloom version: `{bloom_version}` - previous version for package: `{orig_version}` """.format( repository=repository, orig_version=orig_version or 'null', version=version, distro_file=base_info['path'], bloom_version=bloom.__version__, upstream_repo=track_dict['vcs_uri'], release_repo=updated_distribution_file.repositories[repository].release_repository.url, ) body += get_changelog_summary(generate_release_tag(distro)) if base_info['server'] == 'github.com': # Get the github interface gh = get_github_interface() if gh is None: return None # Determine the head org/repo for the pull request head_org = gh.username # The head org will always be gh user head_repo = None base_repo_id = '{org}/{repo}'.format(**base_info) # Check if the github user and the base org are the same if gh.username == base_info['org']: # If it is, then a fork is not necessary head_repo = gh.get_repo(base_info['org'], base_info['repo']) else: info(fmt("@{bf}@!==> @|@!Checking on GitHub for a fork to make the pull request from...")) # It is not, so a fork will be required # Check if a fork already exists on the user's account try: # There are a lot of forks of the ros/rosdistro repository so # listing those forks takes a very long time. # Let's try a little shortcut by checking if the repository of # the same name owned by the current GitHub user, if that # repository exists and is in the same fork network as the # target repo let's take it. # If it is not, we still fall back to listing forks. target_repo = gh.get_repo(base_info['org'], base_info['repo']) target_repo_source = target_repo['full_name'] if target_repo['fork']: target_repo_source = target_repo['source']['full_name'] try: user_repo = gh.get_repo(gh.username, base_info['repo']) if user_repo['fork'] and user_repo['source']['full_name'] == target_repo_source: head_repo = user_repo except GithubException as exc: debug("Received GithubException while checking for fork: {exc}".format(**locals())) # 404 on finding an exact match repo. # Proceed listing all forks. pass if head_repo is None: repo_forks = gh.list_forks(base_info['org'], base_info['repo']) user_forks = [r for r in repo_forks if r.get('owner', {}).get('login', '') == gh.username] # github allows only 1 fork per org as far as I know. We just take the first one. head_repo = user_forks[0] if user_forks else None except GithubException as exc: debug("Received GithubException while checking for fork: {exc}".format(**locals())) pass # 404 or unauthorized, but unauthorized should have been caught above # If not head_repo still, a fork does not exist and must be created if head_repo is None: warning("Could not find a fork of {base_repo_id} on the {gh.username} GitHub account." .format(**locals())) warning("Would you like to create one now?") if not maybe_continue(): warning("Skipping the pull request...") return # Create a fork try: head_repo = gh.create_fork(base_info['org'], base_info['repo']) # Will raise if not successful except GithubException as exc: error("Aborting pull request: {0}".format(exc)) return head_repo = head_repo.get('name', '') info(fmt("@{bf}@!==> @|@!" + "Using this fork to make a pull request from: {head_org}/{head_repo}".format(**locals()))) # Clone the fork info(fmt("@{bf}@!==> @|@!" + "Cloning {0}/{1}...".format(head_org, head_repo))) new_branch = None with temporary_directory() as temp_dir: def _my_run(cmd, msg=None): if msg: info(fmt("@{bf}@!==> @|@!" + sanitize(msg))) else: info(fmt("@{bf}@!==> @|@!" + sanitize(str(cmd)))) from subprocess import check_call check_call(cmd, shell=True) # Use the oauth token to clone rosdistro_url = 'https://{gh.token}:x-oauth-basic@github.com/{base_repo_id}.git'.format(**locals()) fork_template = 'https://{gh.token}:x-oauth-basic@github.com/{head_org}/{head_repo}.git' rosdistro_fork_url = fork_template.format(**locals()) _my_run("mkdir -p {base_info[repo]}".format(**locals())) with change_directory(base_info['repo']): _my_run('git init') branches = [x['name'] for x in gh.list_branches(head_org, head_repo)] new_branch = 'bloom-{repository}-{count}' count = 0 while new_branch.format(repository=repository, count=count) in branches: count += 1 new_branch = new_branch.format(repository=repository, count=count) # Final check info(fmt("@{cf}Pull Request Title: @{yf}" + sanitize(title))) info(fmt("@{cf}Pull Request Body : \n@{yf}" + sanitize(body))) msg = fmt("@!Open a @|@{cf}pull request@| @!@{kf}from@| @!'@|@!@{bf}" + "{head_org}/{head_repo}:{new_branch}".format(**locals()) + "@|@!' @!@{kf}into@| @!'@|@!@{bf}" + "{base_repo_id}:{base_info[branch]}".format(**locals()) + "@|@!'?") info(msg) if interactive and not maybe_continue(): warning("Skipping the pull request...") return _my_run('git checkout -b {new_branch}'.format(**locals())) _my_run("git pull {rosdistro_url} {base_info[branch]}".format(**locals()), "Pulling latest rosdistro branch") rosdistro_index_commit = get_rosdistro_index_commit() if rosdistro_index_commit is not None: _my_run('git reset --hard {rosdistro_index_commit}'.format(**locals())) with open('{0}'.format(base_info['path']), 'w') as f: info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(base_info['path'])) f.write(updated_distro_file_yaml) _my_run('git add {0}'.format(base_info['path'])) _my_run('git commit -m "{0}"'.format(title)) _my_run('git push {rosdistro_fork_url} {new_branch}'.format(**locals()), "Pushing changes to fork") # Open the pull request return gh.create_pull_request(base_info['org'], base_info['repo'], base_info['branch'], head_org, new_branch, title, body) _original_version = None def start_summary(track): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] if 'last_version' not in track_dict or 'release_inc' not in track_dict: _original_version = 'null' else: last_version = track_dict['last_version'] # Actually current version now release_inc = track_dict['release_inc'] _original_version = "{0}-{1}".format(last_version, release_inc) def get_packages(): with inbranch('upstream'): _, _, packages = get_package_data('upstream') return packages def update_summary(track, repository, distro): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] # Actually current version now release_inc = track_dict['release_inc'] version = "{0}-{1}".format(last_version, release_inc) summary_file = get_summary_file() msg = """\ ## {repository} ({distro}) - {version} The packages in the `{repository}` repository were released into the \ `{distro}` distro by running `{cmd}` on `{date}` """.format( repository=repository, distro=distro, date=get_rfc_2822_date(datetime.datetime.now()), cmd=' '.join(sys.argv), version=version ) packages = [p.name for p in get_packages().values()] if len(packages) > 1: msg += "These packages were released:\n" for p in sorted(packages): msg += "- `{0}`\n".format(p) else: package_name = packages[0] msg += "The `{0}` package was released.\n".format(package_name) ignored_packages = get_ignored_packages() if ignored_packages: msg += "\nThese packages were explicitly ignored:\n" for ip in ignored_packages: msg += "- `{0}`\n".format(ip) summary_file = get_summary_file() release_file = get_distribution_file(distro) reps = release_file.repositories distro_version = None release_repo_url = 'unknown' if repository in reps and reps[repository].release_repository is not None: distro_version = reps[repository].release_repository.version release_repo_url = reps[repository].release_repository.url msg += """ Version of package(s) in repository `{repo}`: - upstream repository: {upstream_repo_url} - release repository: {release_repo_url} - rosdistro version: `{rosdistro_pv}` - old version: `{old_pv}` - new version: `{new_pv}` Versions of tools used: - bloom version: `{bloom_v}` - catkin_pkg version: `{catkin_pkg_v}` - rosdep version: `{rosdep_v}` - rosdistro version: `{rosdistro_v}` - vcstools version: `{vcstools_v}` """.format( repo=repository, upstream_repo_url=track_dict['vcs_uri'], release_repo_url=release_repo_url, rosdistro_pv=distro_version or 'null', old_pv=_original_version, new_pv=version, bloom_v=bloom.__version__, catkin_pkg_v=catkin_pkg.__version__, # Until https://github.com/ros-infrastructure/rosdistro/issues/16 rosdistro_v=pkg_resources.require("rosdistro")[0].version, rosdep_v=rosdep2.__version__, vcstools_v=vcstools.__version__.version ) summary_file.write(msg) def _perform_release( repository, track, distro, new_track, interactive, pretend, tracks_dict, override_release_repository_url, override_release_repository_push_url ): # Import here to allow lazy evaluation of commands/git/__init__.py from bloom.commands.git.config import update_track # Ensure the track is complete track_dict = tracks_dict['tracks'][track] track_dict = update_track(track_dict) tracks_dict['tracks'][track] = track_dict # Set the release repositories' remote if given release_repo_url = track_dict.get('release_repo_url', None) if override_release_repository_push_url is not None: if release_repo_url is not None and release_repo_url != override_release_repository_push_url: warning("The 'Release Repository Push URL' is set in the track, " "but is being overridden because the --override-release-repository-push-url option was used.") info(fmt("@{gf}@!==> @|") + "Setting release repository remote url to '{0}'" .format(override_release_repository_push_url)) cmd = 'git remote set-url origin ' + override_release_repository_push_url info(fmt("@{bf}@!==> @|@!") + str(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Setting the remote url failed, exiting.", exit=True) # Permanently override the push url before writing the track dict warning("The release repository push url is being set (permanently) to '{0}'" .format(override_release_repository_push_url)) tracks_dict['release_repo_url'] = override_release_repository_push_url elif override_release_repository_url is not None: if release_repo_url is not None: warning("The 'Release Repository Push URL' is set in the track, " "but is being ignored because the --override-release-repository-url option was used.") elif release_repo_url is not None: # We only get here if the release_repo_url is set and neither override option was set info(fmt("@{gf}@!==> @|") + "Setting release repository remote url to '{0}'" .format(release_repo_url)) cmd = 'git remote set-url origin ' + release_repo_url info(fmt("@{bf}@!==> @|@!") + str(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Setting the remote url failed, exiting.", exit=True) # Check for push permissions try: info(fmt( "@{gf}@!==> @|Testing for push permission on release repository" )) cmd = 'git remote -v' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) # Dry run will authenticate, but not push cmd = 'git push --dry-run' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Cannot push to remote release repository.\n" "Hint: If you just typed in your username/password and you have two-factor authentication," "see:\n http://wiki.ros.org/bloom/Tutorials/GithubManualAuthorization", exit=True) # Write the track config before releasing write_tracks_dict_raw(tracks_dict) # Run the release info(fmt("@{gf}@!==> @|") + "Releasing '{0}' using release track '{1}'" .format(repository, track)) cmd = 'git-bloom-release ' + str(track) if pretend: cmd += ' --pretend' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Release failed, exiting.", exit=True) info(fmt(_success) + "Released '{0}' using release track '{1}' successfully" .format(repository, track)) # Commit the summary update_summary(track, repository, distro) commit_summary() # Check for pushing if interactive: cmd = 'git remote -v' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) info("Releasing complete, push to release repository?") if not maybe_continue(): error("User answered no to continue prompt, aborting.", exit=True) # Push changes to the repository info(fmt("@{gf}@!==> @|") + "Pushing changes to release repository for '{0}'" .format(repository)) cmd = 'git push --all' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --all'?") if not maybe_continue(): error("Pushing changes failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, exiting.", exit=True) info(fmt(_success) + "Pushed changes successfully") # Push tags to the repository info(fmt("@{gf}@!==> @|") + "Pushing tags to release repository for '{0}'" .format(repository)) cmd = 'git push --tags' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --tags'?") if not maybe_continue(): error("Pushing tags failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing tags failed, exiting.", exit=True) info(fmt(_success) + "Pushed tags successfully") def check_for_patches_and_ignores(release_repo_path): warning_messages = [] current_branch = get_current_branch() # Get the list of files in the master branch of the release repository files = ls_tree('master') # Look for any ignore files ignore_files = [] for file_name in files: if file_name.endswith('.ignored'): ignore_files.append(file_name) if ignore_files: warning_messages.append("There are package ignore files: {0}".format(ignore_files)) # Check for .patch files in any patch branches for patch_branch in [b for b in get_branches() if b.lstrip('remotes/origin/').startswith('patches/')]: patch_branch = patch_branch.lstrip('remotes/origin/') if [f for f in ls_tree(patch_branch) if f.endswith('.patch')]: warning_messages.append("There are patches on the branch '{0}'.".format(patch_branch)) # Summarize result if warning_messages: warning("") warning("You are creating a new track, this often means you are releasing for a new distribution.") warning("bloom has detected some patches and/or ignored packages from previous release tracks.") warning("You may wish to migrate patches or duplicate the ignored files from previous releases.") warning("These patches and ignored files are NOT migrated to the new track automatically.") warning("") warning("Potential items to address:") for msg in warning_messages: warning("- {0}".format(msg)) warning("") warning("The release repository is located at '{0}'".format(release_repo_path)) warning("You can modify it in a different shell and continue when finished.") with change_directory(release_repo_path): if not maybe_continue('y'): error("User quit.", exit=True) # with clause should restore the path, regardless of the user's actions checkout(current_branch) def perform_release( repository, track, distro, new_track, interactive, pretend, pull_request_only, override_release_repository_url, override_release_repository_push_url ): # Import here to allow lazy evaluation of commands/git/__init__.py from bloom.commands.git.config import convert_old_bloom_conf from bloom.commands.git.config import edit as edit_track_cmd from bloom.commands.git.config import new as new_track_cmd release_repo = get_release_repo(repository, distro, override_release_repository_url) with change_directory(release_repo.get_path()): def validate_repository_name(repository): return '/' not in repository if not validate_repository_name(repository): error("Invalid repository name: {0}".format(repository), exit=True) # Check to see if the old bloom.conf exists if check_for_bloom_conf(repository): # Convert to a track info("Old bloom.conf file detected.") info(fmt("@{gf}@!==> @|Converting to bloom.conf to track")) convert_old_bloom_conf(None if new_track else distro) upconvert_bloom_to_config_branch() # Check that the track is valid tracks_dict = get_tracks_dict_raw() def create_a_new_track(track, tracks_dict): if not track: error("You must specify a track when creating a new one.", exit=True) if track in tracks_dict['tracks']: warning("Track '{0}' exists, editing...".format(track)) edit_track_cmd(track) tracks_dict = get_tracks_dict_raw() else: # Create a new track called , # copying an existing track if possible, # and overriding the ros_distro warning("Creating track '{0}'...".format(track)) overrides = {'ros_distro': distro} if override_release_repository_push_url is not None: overrides['release_repo_url'] = override_release_repository_push_url new_track_cmd(track, copy_track='', overrides=overrides) tracks_dict = get_tracks_dict_raw() check_for_patches_and_ignores(release_repo.get_path()) return tracks_dict # If new_track, create the new track first if new_track: tracks_dict = create_a_new_track(track, tracks_dict) if track and track not in tracks_dict['tracks']: error("Given track '{0}' does not exist in release repository." .format(track)) info("Available tracks: " + str(tracks_dict['tracks'].keys())) if not track: error("Cannot offer to make a new track, since a track name was not provided.", exit=True) if not maybe_continue(msg="Create a new track called '{0}' now".format(track)): error("User quit.", exit=True) tracks_dict = create_a_new_track(track, tracks_dict) elif not track: tracks = tracks_dict['tracks'].keys() # Error out if there are no tracks if len(tracks) == 0: error("Release repository has no tracks.") info("Manually clone the repository:") info(" git clone {0}".format(release_repo.get_url())) info("And then create a new track:") info(" git-bloom-config new ") error("Run again after creating a track.", exit=True) # Error out if there is more than one track if len(tracks) != 1: error("No track specified and there is not just one track.") error("Please specify one of the available tracks: " + str(tracks), exit=True) # Get the only track track = tracks[0] # Make sure the release repository and the upstream repository are different. track_dict = tracks_dict['tracks'][track] vcs_uri = track_dict.get('vcs_uri') if vcs_uri == release_repo.get_url(): warning("Your RELEASE repository, '{0}', is the same as your UPSTREAM repository, '{1}'." .format(release_repo.get_url(), vcs_uri)) warning("This is not recommended, normally you have separate RELEASE and UPSTREAM repositories.") if not maybe_continue('n', 'Are you sure you want continue'): error("User quit.", exit=True) start_summary(track) if not pull_request_only: _perform_release( repository, track, distro, new_track, interactive, pretend, tracks_dict, override_release_repository_url, override_release_repository_push_url ) if 'BLOOM_NO_ROSDISTRO_PULL_REQUEST' not in os.environ and not pretend: # Propose github pull request info(fmt("@{gf}@!==> @|") + "Generating pull request to distro file located at '{0}'" .format(get_distribution_file_url(distro))) try: pull_request_url = open_pull_request( track, repository, distro, interactive, override_release_repository_url ) if pull_request_url: info(fmt(_success) + "Pull request opened at: {0}".format(pull_request_url)) if 'BLOOM_NO_WEBBROWSER' not in os.environ and platform.system() in ['Darwin']: webbrowser.open(pull_request_url) else: info("The release of your packages was successful, but the pull request failed.") info("Please manually open a pull request by editing the file here: '{0}'" .format(get_distribution_file_url(distro))) info(fmt(_error) + "No pull request opened.") except Exception as e: debug(traceback.format_exc()) error("Failed to open pull request: {0} - {1}".format(type(e).__name__, e), exit=True) def get_argument_parser(): parser = argparse.ArgumentParser(description="Releases a repository which already exists in the ROS distro file.") add = parser.add_argument add('repository', help="repository to run bloom on") add('--list-tracks', '-l', action='store_true', default=False, help="list available tracks for repository") add('--track', '-t', required=False, help="track to run; defaults to rosdistro name") add('--non-interactive', '-y', action='store_true', default=False) add('--ros-distro', '--rosdistro', '-r', required=True, help="determines the ROS distro file used") add('--new-track', '--edit-track', '-n', '-e', action='store_true', default=False, help="if used, a new track will be created before running bloom") add('--pretend', '-s', default=False, action='store_true', help="Pretends to push and release") add('--no-pull-request', default=False, action='store_true', help="Prevents a pull request from being opened after release") add('--no-web', default=False, action='store_true', help="prevents a web browser from being opened at the end") add('--pull-request-only', '-p', default=False, action='store_true', help="skips the release actions and only tries to open a pull request") add('--override-release-repository-url', default=None, help="override the release repository url; " "the 'Release Repository Push URL' track configuration is ignored") add('--override-release-repository-push-url', default=None, help="override the 'Release Repository Push URL' track configuration; " "can be used in conjunction with --override-release-repository-url") return parser _quiet = False def main(sysargs=None): parser = get_argument_parser() parser = add_global_arguments(parser) args = parser.parse_args(sysargs) if args.track is None: args.track = args.ros_distro handle_global_arguments(args) if args.list_tracks: list_tracks(args.repository, args.ros_distro, args.override_release_repository_url) return if args.no_pull_request: os.environ['BLOOM_NO_ROSDISTRO_PULL_REQUEST'] = '1' if args.no_web: os.environ['BLOOM_NO_WEBBROWSER'] = '1' try: os.environ['BLOOM_TRACK'] = args.track disable_git_clone(True) quiet_git_clone_warning(True) perform_release(args.repository, args.track, args.ros_distro, args.new_track, not args.non_interactive, args.pretend, args.pull_request_only, args.override_release_repository_url, args.override_release_repository_push_url) except (KeyboardInterrupt, EOFError) as exc: error("\nReceived '{0}', aborting.".format(type(exc).__name__)) bloom-0.10.7/bloom/commands/update.py000066400000000000000000000113451403641720100174560ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import argparse import atexit import bloom import json import os import sys try: # Python2 from urllib2 import urlopen except ImportError: # Python3 from urllib.request import urlopen from bloom.logging import warning from bloom.util import add_global_arguments from bloom.util import handle_global_arguments from pkg_resources import parse_version from threading import Lock _updater_running = False _updater_lock = Lock() UPDATE_MSG = """\ This version of bloom is '{current}', but the newest available version is '{newest}'. Please update.\ """ def start_updater(): global _updater_running, _updater_lock with _updater_lock: if _updater_running: return _updater_running = True import subprocess subprocess.Popen('bloom-update --quiet', shell=True) @atexit.register def check_for_updates(): if sys.argv[0].endswith('bloom-update'): return user_bloom = os.path.join(os.path.expanduser('~'), '.bloom') if os.path.exists(user_bloom): with open(user_bloom, 'r') as f: raw = f.read() if not raw: return version_dict = json.loads(raw) os.remove(user_bloom) # Remove only on successful parse if type(version_dict) == dict and len(version_dict) == 2 and version_dict['current'] == bloom.__version__: warning(UPDATE_MSG.format(**version_dict)) def get_argument_parser(): parser = argparse.ArgumentParser(description="Checks for updates") add_global_arguments(parser) return parser _quiet = False def info(msg): global _quiet if not _quiet: print(msg) def fetch_update(user_bloom): if os.path.exists(user_bloom): return open(user_bloom, 'w').close() # Touch the file resp = urlopen('https://pypi.python.org/pypi/bloom/json') if sys.version_info.major == 2: pypi_result = json.loads(resp.read()) else: pypi_result = json.loads(resp.read().decode('utf-8')) newest_version = pypi_result['info']['version'] current_version = bloom.__version__ if newest_version and bloom.__version__ != 'unset': if parse_version(bloom.__version__) < parse_version(newest_version): version_dict = { 'current': str(current_version), 'newest': str(newest_version) } with open(user_bloom, 'w') as f: f.write(json.dumps(version_dict)) info(UPDATE_MSG.format(**version_dict)) if _quiet: return else: info("Bloom is up-to-date!") else: info("Cannot determine newest version of bloom.") os.remove(user_bloom) def main(sysargs=None): global _quiet parser = get_argument_parser() args = parser.parse_args(sysargs) handle_global_arguments(args) _quiet = args.quiet user_bloom = os.path.join(os.path.expanduser('~'), '.bloom') try: fetch_update(user_bloom) except Exception as e: if not _quiet: print('Error fetching latest version: ' + str(e), file=sys.stderr) if os.path.exists(user_bloom): os.remove(user_bloom) bloom-0.10.7/bloom/config.py000066400000000000000000000367431403641720100156510ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import os import re import shutil import string import yaml from tempfile import mkdtemp from bloom.git import branch_exists from bloom.git import create_branch from bloom.git import has_changes from bloom.git import get_remotes from bloom.git import get_root from bloom.git import inbranch from bloom.git import show from bloom.git import track_branches from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import sanitize from bloom.util import execute_command from bloom.util import my_copytree from bloom.util import get_distro_list_prompt BLOOM_CONFIG_BRANCH = 'master' PLACEHOLDER_FILE = 'CONTENT_MOVED_TO_{0}_BRANCH'.format(BLOOM_CONFIG_BRANCH.upper()) config_spec = { 'name': { '': 'Name of the repository (used in the archive name)', 'upstream': 'Default value, leave this as upstream if you are unsure' }, 'vcs_uri': { '': '''\ Any valid URI. This variable can be templated, for example an svn url can be templated as such: "https://svn.foo.com/foo/tags/foo-:{version}" where the :{version} token will be replaced with the version for this release.\ ''' }, 'vcs_type': { 'git': 'Upstream URI is a git repository', 'hg': 'Upstream URI is a hg repository', 'svn': 'Upstream URI is a svn repository', 'tar': 'Upstream URI is a tarball' }, 'version': { ':{auto}': '''\ This means the version will be guessed from the devel branch. This means that the devel branch must be set, the devel branch must exist, and there must be a valid package.xml in the upstream devel branch.''', ':{ask}': '''\ This means that the user will be prompted for the version each release. This also means that the upstream devel will be ignored.''', '': '''\ This will be the version used. It must be updated for each new upstream version.''' }, 'release_tag': { ':{version}': '''\ This means that the release tag will match the :{version} tag. This can be further templated, for example: "foo-:{version}" or "v:{version}" This can describe any vcs reference. For git that means {tag, branch, hash}, for hg that means {tag, branch, hash}, for svn that means a revision number. For tar this value doubles as the sub directory (if the repository is in foo/ of the tar ball, putting foo here will cause the contents of foo/ to be imported to upstream instead of foo itself). ''', ':{ask}': '''\ This means the user will be prompted for the release tag on each release. ''', ':{none}': '''\ For svn and tar only you can set the release tag to :{none}, so that it is ignored. For svn this means no revision number is used. ''' }, 'devel_branch': { '': '''\ Branch in upstream repository on which to search for the version. This is used only when version is set to ':{auto}'. ''', }, 'ros_distro': { '': "This can be any valid ROS distro, e.g. %s" % get_distro_list_prompt() }, 'patches': { '': '''\ This can be any valid relative path in the bloom branch. The contents of this folder will be overlaid onto the upstream branch after each import-upstream. Additionally, any package.xml files found in the overlay will have the :{version} string replaced with the current version being released.''', ':{none}': '''\ Use this if you want to disable overlaying of files.''' }, 'release_repo_url': { '': '''\ (optional) Used when pushing to remote release repositories. This is only needed when the release uri which is in the rosdistro file is not writable. This is useful, for example, when a releaser would like to use a ssh url to push rather than a https:// url. ''', ':{none}': '''\ This indicates that the default release url should be used. ''' } } class PromptEntry(object): def __init__(self, name, default=None, values=None, prompt='', spec=None): self.values = values self.name = name self.default = default self.prompt = prompt self.spec = spec def __setattr__(self, key, value): if key == 'default' and self.values: if value not in self.values: error( "Invalid input '{0}' for '{1}', acceptable values: {2}." .format(value, self.name, self.values), exit=True ) object.__setattr__(self, key, value) def __str__(self): msg = fmt('@_' + sanitize(self.name) + ':@|') if self.spec is not None: for key, val in self.spec.items(): msg += '\n ' + key for line in val.splitlines(): msg += '\n ' + line else: msg += '\n ' + self.prompt msg += '\n ' if self.default is None: msg += fmt(" @![@{yf}None@|@!]@|: ") else: msg += fmt(" @!['@{yf}" + sanitize(self.default) + "@|@!']@|: ") return msg ACTION_LIST_HISTORY = [ [ 'bloom-export-upstream :{vcs_local_uri} :{vcs_type}' ' --tag :{release_tag} --display-uri :{vcs_uri}' ' --name :{name} --output-dir :{archive_dir_path}', 'git-bloom-import-upstream :{archive_path} :{patches}' ' --release-version :{version} --replace', 'git-bloom-generate -y rosrelease :{ros_distro}' ' --source upstream -i :{release_inc}', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc}' ], [ 'bloom-export-upstream :{vcs_local_uri} :{vcs_type}' ' --tag :{release_tag} --display-uri :{vcs_uri}' ' --name :{name} --output-dir :{archive_dir_path}', 'git-bloom-import-upstream :{archive_path} :{patches}' ' --release-version :{version} --replace', 'git-bloom-generate -y rosrelease :{ros_distro}' ' --source upstream -i :{release_inc}', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc}', 'git-bloom-generate -y rosrpm --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc}' ], [ 'bloom-export-upstream :{vcs_local_uri} :{vcs_type}' ' --tag :{release_tag} --display-uri :{vcs_uri}' ' --name :{name} --output-dir :{archive_dir_path}', 'git-bloom-import-upstream :{archive_path} :{patches}' ' --release-version :{version} --replace', 'git-bloom-generate -y rosrelease :{ros_distro}' ' --source upstream -i :{release_inc}', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name ubuntu', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name debian --os-not-required', 'git-bloom-generate -y rosrpm --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc}' ], [ 'bloom-export-upstream :{vcs_local_uri} :{vcs_type}' ' --tag :{release_tag} --display-uri :{vcs_uri}' ' --name :{name} --output-dir :{archive_dir_path}', 'git-bloom-import-upstream :{archive_path} :{patches}' ' --release-version :{version} --replace', 'git-bloom-generate -y rosrelease :{ros_distro}' ' --source upstream -i :{release_inc}', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name ubuntu', 'git-bloom-generate -y rosdebian --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name debian --os-not-required', 'git-bloom-generate -y rosrpm --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name fedora', 'git-bloom-generate -y rosrpm --prefix release/:{ros_distro}' ' :{ros_distro} -i :{release_inc} --os-name rhel', ] ] DEFAULT_TEMPLATE = { 'name': PromptEntry('Repository Name', spec=config_spec['name'], default='upstream'), 'vcs_uri': PromptEntry('Upstream Repository URI', spec=config_spec['vcs_uri']), 'vcs_type': PromptEntry( 'Upstream VCS Type', default='git', spec=config_spec['vcs_type'], values=['git', 'hg', 'svn', 'tar']), 'version': PromptEntry('Version', default=':{auto}', spec=config_spec['version']), 'release_tag': PromptEntry('Release Tag', default=':{version}', spec=config_spec['release_tag']), 'devel_branch': PromptEntry('Upstream Devel Branch', spec=config_spec['devel_branch']), 'patches': PromptEntry('Patches Directory', spec=config_spec['patches']), 'ros_distro': PromptEntry('ROS Distro', default='indigo', spec=config_spec['ros_distro']), 'release_repo_url': PromptEntry('Release Repository Push URL', spec=config_spec['release_repo_url']), 'release_inc': 0, 'actions': ACTION_LIST_HISTORY[-1] } CUSTOM_TEMPLATE = { 'reference': ':{ask}', 'patches': ':{name}' } config_template = { 'third-party': CUSTOM_TEMPLATE, None: {} } def verify_track(track_name, track): upconvert_bloom_to_config_branch() for entry in DEFAULT_TEMPLATE: if entry not in track: error("Track '{0}' is missing configuration ".format(track_name) + "'{0}', it may be out of date, please run 'git-bloom-config edit {1}'." .format(entry, track_name), exit=True) class ConfigTemplate(string.Template): delimiter = ':' def template_str(line, settings): t = ConfigTemplate(line) return t.substitute(settings) def write_tracks_dict_raw(tracks_dict, cmt_msg=None, directory=None): upconvert_bloom_to_config_branch() cmt_msg = cmt_msg if cmt_msg is not None else 'Modified tracks.yaml' with inbranch(BLOOM_CONFIG_BRANCH): with open('tracks.yaml', 'w') as f: f.write(yaml.safe_dump(tracks_dict, indent=2, default_flow_style=False)) execute_command('git add tracks.yaml', cwd=directory) execute_command('git commit --allow-empty -m "{0}"'.format(cmt_msg), cwd=directory) version_regex = re.compile(r'^\d+\.\d+\.\d+$') def validate_track_versions(tracks_dict): for track in tracks_dict['tracks'].values(): if 'version' in track: if track['version'] in [':{ask}', ':{auto}']: continue if version_regex.match(track['version']) is None: raise ValueError( "Invalid version '{0}', it must be formatted as 'MAJOR.MINOR.PATCH'" .format(track['version'])) def get_tracks_dict_raw(directory=None): upconvert_bloom_to_config_branch() if not branch_exists(BLOOM_CONFIG_BRANCH): info("Creating '{0}' branch.".format(BLOOM_CONFIG_BRANCH)) create_branch(BLOOM_CONFIG_BRANCH, orphaned=True, directory=directory) tracks_yaml = show(BLOOM_CONFIG_BRANCH, 'tracks.yaml', directory=directory) if not tracks_yaml: write_tracks_dict_raw( {'tracks': {}}, 'Initial tracks.yaml', directory=directory ) tracks_yaml = show(BLOOM_CONFIG_BRANCH, 'tracks.yaml', directory=directory) tracks_dict = yaml.safe_load(tracks_yaml) validate_track_versions(tracks_dict) return tracks_dict _has_checked_bloom_branch = False def check_for_multiple_remotes(): if get_root() is None: return remotes = get_remotes() if len(remotes) < 0: error("Current git repository has no remotes. " "If you are running bloom-release, please change directories.", exit=True) if len(remotes) > 1: error("Current git repository has multiple remotes. " "If you are running bloom-release, please change directories.", exit=True) def upconvert_bloom_to_config_branch(): global _has_checked_bloom_branch if _has_checked_bloom_branch: return # Assert that this repository does not have multiple remotes check_for_multiple_remotes() if get_root() is None: # Not a git repository return track_branches(['bloom', BLOOM_CONFIG_BRANCH]) if show('bloom', PLACEHOLDER_FILE) is not None: return if show('bloom', 'bloom.conf') is not None: # Wait for the bloom.conf upconvert... return if not branch_exists('bloom'): return _has_checked_bloom_branch = True info("Moving configurations from deprecated 'bloom' branch " "to the '{0}' branch.".format(BLOOM_CONFIG_BRANCH)) tmp_dir = mkdtemp() git_root = get_root() try: # Copy the new upstream source into the temporary directory with inbranch('bloom'): ignores = ('.git', '.gitignore', '.svn', '.hgignore', '.hg', 'CVS') configs = os.path.join(tmp_dir, 'configs') my_copytree(git_root, configs, ignores) if [x for x in os.listdir(os.getcwd()) if x not in ignores]: execute_command('git rm -rf ./*') with open(PLACEHOLDER_FILE, 'w') as f: f.write("""\ This branch ('bloom') has been deprecated in favor of storing settings and overlay files in the master branch. Please goto the master branch for anything which referenced the bloom branch. You can delete this branch at your convenience. """) execute_command('git add ' + PLACEHOLDER_FILE) if has_changes(): execute_command('git commit -m "DEPRECATING BRANCH"') if not branch_exists(BLOOM_CONFIG_BRANCH): info("Creating '{0}' branch.".format(BLOOM_CONFIG_BRANCH)) create_branch(BLOOM_CONFIG_BRANCH, orphaned=True) with inbranch(BLOOM_CONFIG_BRANCH): my_copytree(configs, git_root) execute_command('git add ./*') if has_changes(): execute_command('git commit -m ' '"Moving configs from bloom branch"') finally: # Clean up if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) bloom-0.10.7/bloom/generators/000077500000000000000000000000001403641720100161665ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/README.md000066400000000000000000000020411403641720100174420ustar00rootroot00000000000000Bloom generators ================ Generators read catkin/ament package manifests and generate files necessary to package releases for various target platforms. ## Build types Prior to version 0.6.0, Bloom used a single template for all packages which supported packages built with cmake, including catkin packages. Bloom now supports different build types by inspecting the `build_type` tag in package manifests. Templates for a build type are stored in subdirectories of the platform's templates directory named for the build type. As an example the templates for the `ament_cmake` build type for debian packages is stored in [bloom/generators/debian/templates/ament_cmake](debian/templates/ament_cmake). To add support for a new build type create a new templates subdirectory for it in your target platform's `templates` directory, and add any necessary supporting code to the generator for build type specific substitutions.For examples search for "Build-type specific substitutions" in [bloom/generators/debian/generator.py](debian/generator.py). bloom-0.10.7/bloom/generators/__init__.py000077500000000000000000000037201403641720100203040ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from .common import BloomGenerator from .common import GeneratorError from .common import list_generators from .common import load_generator from .common import resolve_dependencies from .common import update_rosdep __all__ = [ 'BloomGenerator', 'GeneratorError', 'list_generators', 'load_generator', 'resolve_dependencies', 'update_rosdep' ] bloom-0.10.7/bloom/generators/common.py000066400000000000000000000326001403641720100200310ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import pkg_resources import sys import traceback from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.rosdistro_api import get_distribution_type from bloom.rosdistro_api import get_index from bloom.rosdistro_api import get_python_version from bloom.util import code from bloom.util import maybe_continue from bloom.util import print_exc try: from rosdep2 import create_default_installer_context from rosdep2.catkin_support import get_catkin_view from rosdep2.lookup import ResolutionError import rosdep2.catkin_support except ImportError as err: debug(traceback.format_exc()) error("rosdep was not detected, please install it.", exit=True) BLOOM_GROUP = 'bloom.generators' DEFAULT_ROS_DISTRO = 'indigo' def list_generators(): generators = [] for entry_point in pkg_resources.iter_entry_points(group=BLOOM_GROUP): generators.append(entry_point.name) return generators def load_generator(generator_name): for entry_point in pkg_resources.iter_entry_points(group=BLOOM_GROUP): if entry_point.name == generator_name: return entry_point.load() view_cache = {} def get_view(os_name, os_version, ros_distro): global view_cache key = os_name + os_version + ros_distro if key not in view_cache: value = get_catkin_view(ros_distro, os_name, os_version, False) view_cache[key] = value return view_cache[key] def invalidate_view_cache(): global view_cache view_cache = {} def update_rosdep(): info("Running 'rosdep update'...") try: rosdep2.catkin_support.update_rosdep() except: print_exc(traceback.format_exc()) error("Failed to update rosdep, did you run 'rosdep init' first?", exit=True) def resolve_more_for_os(rosdep_key, view, installer, os_name, os_version): """ Resolve rosdep key to dependencies and installer key. (This was copied from rosdep2.catkin_support) :param os_name: OS name, e.g. 'ubuntu' :returns: resolved key, resolved installer key, and default installer key :raises: :exc:`rosdep2.ResolutionError` """ d = view.lookup(rosdep_key) ctx = create_default_installer_context() os_installers = ctx.get_os_installer_keys(os_name) default_os_installer = ctx.get_default_os_installer_key(os_name) inst_key, rule = d.get_rule_for_platform(os_name, os_version, os_installers, default_os_installer) assert inst_key in os_installers return installer.resolve(rule), inst_key, default_os_installer def package_conditional_context(ros_distro): """ Creates a dict containing the conditional evaluation context for package.xml format three packages. :param ros_distro: The codename of the rosdistro to generate context for. :returns: dict defining ROS_VERSION and ROS_DISTRO. """ if get_index().version < 4: error("Bloom requires a version 4 or greater rosdistro index to support package format 3.", exit=True) distribution_type = get_distribution_type(ros_distro) if distribution_type == 'ros1': ros_version = '1' elif distribution_type == 'ros2': ros_version = '2' else: error("Bloom cannot cope with distribution_type '{0}'".format( distribution_type), exit=True) python_version = get_python_version(ros_distro) if python_version is None: error( 'No python_version found in the rosdistro index. ' 'The rosdistro index must include this key for bloom to work correctly.', exit=True) elif python_version == 2: ros_python_version = '2' elif python_version == 3: ros_python_version = '3' else: error("Bloom cannot cope with python_version '{0}'".format( python_version), exit=True) return { 'ROS_VERSION': ros_version, 'ROS_DISTRO': ros_distro, 'ROS_PYTHON_VERSION': ros_python_version, } def evaluate_package_conditions(package, ros_distro): """ Evaluates a package's conditional fields if it supports them. :param package: The package to be evaluated. :param ros_distro: The codename of the rosdistro use for context. :returns: None. The given package will be modified. """ # Conditional fields were introduced in package format 3. # Earlier formats should have their conditions evaluated with no context so # the evaluated_condition is set to True in all cases. if package.package_format >= 3: package.evaluate_conditions(package_conditional_context(ros_distro)) def resolve_rosdep_key( key, os_name, os_version, ros_distro=None, ignored=None, retry=True ): ignored = ignored or [] ctx = create_default_installer_context() try: installer_key = ctx.get_default_os_installer_key(os_name) except KeyError: BloomGenerator.exit("Could not determine the installer for '{0}'" .format(os_name)) installer = ctx.get_installer(installer_key) ros_distro = ros_distro or DEFAULT_ROS_DISTRO view = get_view(os_name, os_version, ros_distro) try: return resolve_more_for_os(key, view, installer, os_name, os_version) except (KeyError, ResolutionError) as exc: debug(traceback.format_exc()) if key in ignored: return None, None, None if isinstance(exc, KeyError): error("Could not resolve rosdep key '{0}'".format(key)) returncode = code.GENERATOR_NO_SUCH_ROSDEP_KEY else: error("Could not resolve rosdep key '{0}' for distro '{1}':" .format(key, os_version)) info(str(exc), use_prefix=False) returncode = code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO if retry: error("Try to resolve the problem with rosdep and then continue.") if maybe_continue(): update_rosdep() invalidate_view_cache() return resolve_rosdep_key(key, os_name, os_version, ros_distro, ignored, retry=True) BloomGenerator.exit("Failed to resolve rosdep key '{0}', aborting." .format(key), returncode=returncode) def default_fallback_resolver(key, peer_packages): BloomGenerator.exit("Failed to resolve rosdep key '{0}', aborting." .format(key), returncode=code.GENERATOR_NO_SUCH_ROSDEP_KEY) def resolve_dependencies( keys, os_name, os_version, ros_distro=None, peer_packages=None, fallback_resolver=None ): ros_distro = ros_distro or DEFAULT_ROS_DISTRO peer_packages = peer_packages or [] fallback_resolver = fallback_resolver or default_fallback_resolver resolved_keys = {} keys = [k.name for k in keys] for key in keys: resolved_key, installer_key, default_installer_key = \ resolve_rosdep_key(key, os_name, os_version, ros_distro, peer_packages, retry=True) # Do not compare the installer key here since this is a general purpose function # They installer is verified in the OS specific generator, when the keys are pre-checked. if resolved_key is None: resolved_key = fallback_resolver(key, peer_packages) resolved_keys[key] = resolved_key return resolved_keys class GeneratorError(Exception): def __init__(self, msg, returncode=code.UNKNOWN): super(GeneratorError, self).__init__("Error running generator: " + msg) self.returncode = returncode @staticmethod def excepthook(etype, value, traceback): GeneratorError.sysexcepthook(etype, value, traceback) if isinstance(value, GeneratorError): sys.exit(value.returncode) sys.excepthook, sysexcepthook = excepthook.__func__, staticmethod(sys.excepthook) class BloomGenerator(object): """ Abstract generator class, from which all bloom generators inherit. """ generator_type = None title = 'no title' description = None help = None @classmethod def exit(cls, msg, returncode=code.UNKNOWN): raise GeneratorError(msg, returncode) def prepare_arguments(self, parser): """ Argument preparation hook, should be implemented in child class :param parser: argparse.ArgumentParser on which to call add_argument() """ pass def handle_arguments(self, args): """ Hook to handle parsed arguments from argparse """ debug("BloomGenerator.handle_arguments: got args -> " + str(args)) def summarize(self): """ Summarize the command, consider listing configurations here """ info("Running " + self.title + " generator") def get_branching_arguments(self): """ Return a list of tuples, each representing parameters for branching. Override this to return something other than [] if this generator needs to produce branches. The tuples can either be singular (destination,), or contain two elements (destination, source). Optionally, a third tuple element can be a bool indicating if git-bloom-branch should be interactive: (destination, source, interactive) :returns: list of tuples containing arguments for git-bloom-branch """ return [] def pre_modify(self): """ Hook for last minute checks This is the last call before the generator is expected to start performing modifications to the repository. :returns: return code, return 0 or None for OK, anything else on error """ return 0 def pre_branch(self, destination, source): """ Pre-branching hook :param destination: destination branch name :param source: source branch name :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def post_branch(self, destination, source): """ Post-branching hook :param destination: destination branch name :param source: source branch name :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def pre_export_patches(self, branch_name): """ Pre-patch-export hook :param branch_name: name of the branch patches are being exported from :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def post_export_patches(self, branch_name): """ Post-patch-export hook :param branch_name: name of the branch patches are being exported from :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def pre_rebase(self, branch_name): """ Pre-rebase hook :param branch_name: name of the branch rebase is being done on :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def post_rebase(self, branch_name): """ Post-rebase hook :param branch_name: name of the branch rebase is being done on :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def pre_patch(self, branch_name): """ Pre-patching hook :param branch_name: name of the branch being patched :returns: return code, return 0 or None for OK, anythign else on error """ return 0 def post_patch(self, branch_name): """ Post-patching hook :param branch_name: name of the branch being patched :returns: return code, return 0 or None for OK, anythign else on error """ return 0 bloom-0.10.7/bloom/generators/debian/000077500000000000000000000000001403641720100174105ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/__init__.py000077500000000000000000000002141403641720100215210ustar00rootroot00000000000000from .generator import DebianGenerator from .generator import sanitize_package_name __all__ = ['DebianGenerator', 'sanitize_package_name'] bloom-0.10.7/bloom/generators/debian/generate_cmd.py000066400000000000000000000137771403641720100224160ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import os import sys import traceback from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.generators.debian.generator import generate_substitutions_from_package from bloom.generators.debian.generator import place_template_files from bloom.generators.debian.generator import process_template_files from bloom.util import get_distro_list_prompt try: from rosdep2 import create_default_installer_context except ImportError: debug(traceback.format_exc()) error("rosdep was not detected, please install it.", exit=True) try: from catkin_pkg.packages import find_packages except ImportError: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", exit=True) def prepare_arguments(parser): add = parser.add_argument add('package_path', nargs='?', help="path to or containing the package.xml of a package") action = parser.add_mutually_exclusive_group(required=False) add = action.add_argument add('--place-template-files', action='store_true', help="places debian/* template files only") add('--process-template-files', action='store_true', help="processes templates in debian/* only") add = parser.add_argument add('--os-name', help='OS name, e.g. ubuntu, debian') add('--os-version', help='OS version or codename, e.g. precise, wheezy') add('--ros-distro', help="ROS distro, e.g. %s (used for rosdep)" % get_distro_list_prompt()) add('-i', '--debian-inc', help="debian increment number", default='0') add('--native', action='store_true', help="generate native package") return parser def get_subs(pkg, os_name, os_version, ros_distro, deb_inc=0, native=False): return generate_substitutions_from_package( pkg, os_name, os_version, ros_distro, deb_inc=deb_inc, native=native ) def main(args=None, get_subs_fn=None): get_subs_fn = get_subs_fn or get_subs _place_template_files = True _process_template_files = True package_path = os.getcwd() if args is not None: package_path = args.package_path or os.getcwd() _place_template_files = args.place_template_files _process_template_files = args.process_template_files pkgs_dict = find_packages(package_path) if len(pkgs_dict) == 0: sys.exit("No packages found in path: '{0}'".format(package_path)) if len(pkgs_dict) > 1: sys.exit("Multiple packages found, " "this tool only supports one package at a time.") os_data = create_default_installer_context().get_os_name_and_version() os_name, os_version = os_data ros_distro = os.environ.get('ROS_DISTRO', 'indigo') # Allow args overrides os_name = args.os_name or os_name os_version = args.os_version or os_version ros_distro = args.ros_distro or ros_distro # Summarize info(fmt("@!@{gf}==> @|") + fmt("Generating debs for @{cf}%s:%s@| for package(s) %s" % (os_name, os_version, [p.name for p in pkgs_dict.values()]))) for path, pkg in pkgs_dict.items(): template_files = None try: subs = get_subs_fn(pkg, os_name, os_version, ros_distro, args.debian_inc, args.native) if _place_template_files: # Place template files place_template_files(path, pkg.get_build_type()) if _process_template_files: # Just process existing template files template_files = process_template_files(path, subs) if not _place_template_files and not _process_template_files: # If neither, do both place_template_files(path, pkg.get_build_type()) template_files = process_template_files(path, subs) if template_files is not None: for template_file in template_files: os.remove(os.path.normpath(template_file)) except Exception as exc: debug(traceback.format_exc()) error(type(exc).__name__ + ": " + str(exc), exit=True) except (KeyboardInterrupt, EOFError): sys.exit(1) # This describes this command to the loader description = dict( title='debian', description="Generates debian packaging files for a catkin package", main=main, prepare_arguments=prepare_arguments ) bloom-0.10.7/bloom/generators/debian/generator.py000066400000000000000000001164411403641720100217570ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import collections import datetime import io import json import os import pkg_resources import re import shutil import sys import traceback # Python 2/3 support. try: from configparser import SafeConfigParser except ImportError: from ConfigParser import SafeConfigParser from dateutil import tz from pkg_resources import parse_version from bloom.generators import BloomGenerator from bloom.generators import GeneratorError from bloom.generators import resolve_dependencies from bloom.generators import update_rosdep from bloom.generators.common import default_fallback_resolver from bloom.generators.common import invalidate_view_cache from bloom.generators.common import evaluate_package_conditions from bloom.generators.common import resolve_rosdep_key from bloom.git import inbranch from bloom.git import get_branches from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import has_changes from bloom.git import show from bloom.git import tag_exists from bloom.logging import ansi from bloom.logging import debug from bloom.logging import enable_drop_first_log_prefix from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import is_debug from bloom.logging import warning from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config from bloom.packages import get_package_data from bloom.util import code from bloom.util import to_unicode from bloom.util import execute_command from bloom.util import get_rfc_2822_date from bloom.util import maybe_continue try: from catkin_pkg.changelog import get_changelog_from_path from catkin_pkg.changelog import CHANGELOG_FILENAME except ImportError as err: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", exit=True) try: import rosdistro except ImportError as err: debug(traceback.format_exc()) error("rosdistro was not detected, please install it.", exit=True) try: import em except ImportError: debug(traceback.format_exc()) error("empy was not detected, please install it.", exit=True) # Fix unicode bug in empy # This should be removed once upstream empy is fixed # See: https://github.com/ros-infrastructure/bloom/issues/196 try: em.str = unicode em.Stream.write_old = em.Stream.write em.Stream.write = lambda self, data: em.Stream.write_old(self, data.encode('utf8')) except NameError: pass # End fix # Drop the first log prefix for this command enable_drop_first_log_prefix(True) TEMPLATE_EXTENSION = '.em' def __place_template_folder(group, src, dst, gbp=False): template_files = pkg_resources.resource_listdir(group, src) # For each template, place for template_file in template_files: if not gbp and os.path.basename(template_file) == 'gbp.conf.em': debug("Skipping template '{0}'".format(template_file)) continue template_path = os.path.join(src, template_file) template_dst = os.path.join(dst, template_file) if pkg_resources.resource_isdir(group, template_path): debug("Recursing on folder '{0}'".format(template_path)) __place_template_folder(group, template_path, template_dst, gbp) else: try: debug("Placing template '{0}'".format(template_path)) template = pkg_resources.resource_string(group, template_path) template_abs_path = pkg_resources.resource_filename(group, template_path) except IOError as err: error("Failed to load template " "'{0}': {1}".format(template_file, str(err)), exit=True) if not os.path.exists(dst): os.makedirs(dst) if os.path.exists(template_dst): debug("Not overwriting existing file '{0}'".format(template_dst)) else: with io.open(template_dst, 'w', encoding='utf-8') as f: if not isinstance(template, str): template = template.decode('utf-8') # Python 2 API needs a `unicode` not a utf-8 string. elif sys.version_info.major == 2: template = template.decode('utf-8') f.write(template) shutil.copystat(template_abs_path, template_dst) def place_template_files(path, build_type, gbp=False): info(fmt("@!@{bf}==>@| Placing templates files in the 'debian' folder.")) debian_path = os.path.join(path, 'debian') # Create/Clean the debian folder if not os.path.exists(debian_path): os.makedirs(debian_path) # Place template files group = 'bloom.generators.debian' templates = os.path.join('templates', build_type) __place_template_folder(group, templates, debian_path, gbp) def summarize_dependency_mapping(data, deps, build_deps, resolved_deps): if len(deps) == 0 and len(build_deps) == 0: return info("Package '" + data['Package'] + "' has dependencies:") header = " " + ansi('boldoff') + ansi('ulon') + \ "rosdep key => " + data['Distribution'] + \ " key" + ansi('reset') template = " " + ansi('cyanf') + "{0:<20} " + ansi('purplef') + \ "=> " + ansi('cyanf') + "{1}" + ansi('reset') if len(deps) != 0: info(ansi('purplef') + "Run Dependencies:" + ansi('reset')) info(header) for key in [d.name for d in deps]: info(template.format(key, resolved_deps[key])) if len(build_deps) != 0: info(ansi('purplef') + "Build and Build Tool Dependencies:" + ansi('reset')) info(header) for key in [d.name for d in build_deps]: info(template.format(key, resolved_deps[key])) def format_depends(depends, resolved_deps): versions = { 'version_lt': '<<', 'version_lte': '<=', 'version_eq': '=', 'version_gte': '>=', 'version_gt': '>>' } formatted = [] for d in depends: for resolved_dep in resolved_deps[d.name]: version_depends = [k for k in versions.keys() if getattr(d, k, None) is not None] if not version_depends: formatted.append(resolved_dep) else: for v in version_depends: formatted.append("{0} ({1} {2})".format( resolved_dep, versions[v], getattr(d, v))) return formatted def format_description(value): """ Format proper string following Debian control file formatting rules. Treat first line in given string as synopsis, everything else as a single, large paragraph. Future extensions of this function could convert embedded newlines and / or html into paragraphs in the Description field. https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description """ value = debianize_string(value) # NOTE: bit naive, only works for 'properly formatted' pkg descriptions (ie: # 'Text. Text'). Extra space to avoid splitting on arbitrary sequences # of characters broken up by dots (version nrs fi). parts = value.split('. ', 1) if len(parts) == 1 or len(parts[1]) == 0: # most likely single line description return value # format according to rules in linked field documentation return u"{0}.\n {1}".format(parts[0], parts[1].strip()) def get_changelogs(package, releaser_history=None): if releaser_history is None: warning("No historical releaser history, using current maintainer name " "and email for each versioned changelog entry.") releaser_history = {} if is_debug(): import logging logging.basicConfig() import catkin_pkg catkin_pkg.changelog.log.setLevel(logging.DEBUG) package_path = os.path.abspath(os.path.dirname(package.filename)) changelog_path = os.path.join(package_path, CHANGELOG_FILENAME) if os.path.exists(changelog_path): changelog = get_changelog_from_path(changelog_path) changelogs = [] maintainer = (package.maintainers[0].name, package.maintainers[0].email) for version, date, changes in changelog.foreach_version(reverse=True): changes_str = [] date_str = get_rfc_2822_date(date) for item in changes: changes_str.extend([' ' + i for i in to_unicode(item).splitlines()]) # Each entry has (version, date, changes, releaser, releaser_email) releaser, email = releaser_history.get(version, maintainer) changelogs.append(( version, date_str, '\n'.join(changes_str), releaser, email )) return changelogs else: warning("No {0} found for package '{1}'" .format(CHANGELOG_FILENAME, package.name)) return [] def missing_dep_resolver(key, peer_packages): if key in peer_packages: return [sanitize_package_name(key)] return default_fallback_resolver(key, peer_packages) def generate_substitutions_from_package( package, os_name, os_version, ros_distro, installation_prefix='/usr', deb_inc=0, peer_packages=None, releaser_history=None, fallback_resolver=None, native=False ): peer_packages = peer_packages or [] data = {} # Name, Version, Description data['Name'] = package.name data['Version'] = package.version data['Description'] = format_description(package.description) # Websites websites = [str(url) for url in package.urls if url.type == 'website'] homepage = websites[0] if websites else '' if homepage == '': warning("No homepage set, defaulting to ''") data['Homepage'] = homepage # Debian Increment Number data['DebianInc'] = '' if native else '-{0}'.format(deb_inc) # Debian Package Format data['format'] = 'native' if native else 'quilt' # Package name data['Package'] = sanitize_package_name(package.name) # Installation prefix data['InstallationPrefix'] = installation_prefix # Resolve dependencies evaluate_package_conditions(package, ros_distro) depends = [ dep for dep in (package.run_depends + package.buildtool_export_depends) if dep.evaluated_condition is not False] build_depends = [ dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends) if dep.evaluated_condition is not False] replaces = [ dep for dep in package.replaces if dep.evaluated_condition is not False] conflicts = [ dep for dep in package.conflicts if dep.evaluated_condition is not False] unresolved_keys = depends + build_depends + replaces + conflicts # The installer key is not considered here, but it is checked when the keys are checked before this resolved_deps = resolve_dependencies(unresolved_keys, os_name, os_version, ros_distro, peer_packages + [d.name for d in (replaces + conflicts)], fallback_resolver) data['Depends'] = sorted( set(format_depends(depends, resolved_deps)) ) data['BuildDepends'] = sorted( set(format_depends(build_depends, resolved_deps)) ) data['Replaces'] = sorted( set(format_depends(replaces, resolved_deps)) ) data['Conflicts'] = sorted( set(format_depends(conflicts, resolved_deps)) ) # Build-type specific substitutions. build_type = package.get_build_type() if build_type == 'catkin': pass elif build_type == 'cmake': pass elif build_type == 'ament_cmake': pass elif build_type == 'ament_python': # Don't set the install-scripts flag if it's already set in setup.cfg. package_path = os.path.abspath(os.path.dirname(package.filename)) setup_cfg_path = os.path.join(package_path, 'setup.cfg') data['pass_install_scripts'] = True if os.path.isfile(setup_cfg_path): setup_cfg = SafeConfigParser() setup_cfg.read([setup_cfg_path]) if ( setup_cfg.has_option('install', 'install-scripts') or setup_cfg.has_option('install', 'install_scripts') ): data['pass_install_scripts'] = False else: error( "Build type '{}' is not supported by this version of bloom.". format(build_type), exit=True) # Set the distribution data['Distribution'] = os_version # Use the time stamp to set the date strings stamp = datetime.datetime.now(tz.tzlocal()) data['Date'] = stamp.strftime('%a, %d %b %Y %T %z') data['YYYY'] = stamp.strftime('%Y') # Maintainers maintainers = [] for m in package.maintainers: maintainers.append(str(m)) data['Maintainer'] = maintainers[0] data['Maintainers'] = ', '.join(maintainers) # Changelog changelogs = get_changelogs(package, releaser_history) if changelogs and package.version not in [x[0] for x in changelogs]: warning("") warning("A CHANGELOG.rst was found, but no changelog for this version was found.") warning("You REALLY should have a entry (even a blank one) for each version of your package.") warning("") if not changelogs: # Ensure at least a minimal changelog changelogs = [] if package.version not in [x[0] for x in changelogs]: changelogs.insert(0, ( package.version, get_rfc_2822_date(datetime.datetime.now()), ' * Autogenerated, no changelog for this version found in CHANGELOG.rst.', package.maintainers[0].name, package.maintainers[0].email )) bad_changelog = False # Make sure that the first change log is the version being released if package.version != changelogs[0][0]: error("") error("The version of the first changelog entry '{0}' is not the " "same as the version being currently released '{1}'." .format(package.version, changelogs[0][0])) bad_changelog = True # Make sure that the current version is the latest in the changelog for changelog in changelogs: if parse_version(package.version) < parse_version(changelog[0]): error("") error("There is at least one changelog entry, '{0}', which has a " "newer version than the version of package '{1}' being released, '{2}'." .format(changelog[0], package.name, package.version)) bad_changelog = True if bad_changelog: error("This is almost certainly by mistake, you should really take a " "look at the changelogs for the package you are releasing.") error("") if not maybe_continue('n', 'Continue anyways'): sys.exit("User quit.") data['changelogs'] = changelogs # Use debhelper version 7 for oneric, otherwise 9 data['debhelper_version'] = 7 if os_version in ['oneiric'] else 9 # Summarize dependencies summarize_dependency_mapping(data, depends, build_depends, resolved_deps) # Copyright licenses = [] separator = '\n' + '=' * 80 + '\n\n' for l in package.licenses: if hasattr(l, 'file') and l.file is not None: license_file = os.path.join(os.path.dirname(package.filename), l.file) if not os.path.exists(license_file): error("License file '{}' is not found.". format(license_file), exit=True) license_text = open(license_file, 'r').read() if not license_text.endswith('\n'): license_text += '\n' licenses.append(license_text) data['Copyright'] = separator.join(licenses) def convertToUnicode(obj): if sys.version_info.major == 2: if isinstance(obj, str): return unicode(obj.decode('utf8')) elif isinstance(obj, unicode): return obj else: if isinstance(obj, bytes): return str(obj.decode('utf8')) elif isinstance(obj, str): return obj if isinstance(obj, list): for i, val in enumerate(obj): obj[i] = convertToUnicode(val) return obj elif isinstance(obj, type(None)): return None elif isinstance(obj, tuple): obj_tmp = list(obj) for i, val in enumerate(obj_tmp): obj_tmp[i] = convertToUnicode(obj_tmp[i]) return tuple(obj_tmp) elif isinstance(obj, int): return obj raise RuntimeError('need to deal with type %s' % (str(type(obj)))) for item in data.items(): data[item[0]] = convertToUnicode(item[1]) return data def __process_template_folder(path, subs): items = os.listdir(path) processed_items = [] for item in list(items): item = os.path.abspath(os.path.join(path, item)) if os.path.basename(item) in ['.', '..', '.git', '.svn']: continue if os.path.isdir(item): sub_items = __process_template_folder(item, subs) processed_items.extend([os.path.join(item, s) for s in sub_items]) if not item.endswith(TEMPLATE_EXTENSION): continue with open(item, 'r') as f: template = f.read() # Remove extension template_path = item[:-len(TEMPLATE_EXTENSION)] # Expand template info("Expanding '{0}' -> '{1}'".format( os.path.relpath(item), os.path.relpath(template_path))) result = em.expand(template, **subs) # Don't write an empty file if len(result) == 0 and \ os.path.basename(template_path) in ['copyright']: processed_items.append(item) continue # Write the result with io.open(template_path, 'w', encoding='utf-8') as f: if sys.version_info.major == 2: result = result.decode('utf-8') f.write(result) # Copy the permissions shutil.copymode(item, template_path) processed_items.append(item) return processed_items def process_template_files(path, subs): info(fmt("@!@{bf}==>@| In place processing templates in 'debian' folder.")) debian_dir = os.path.join(path, 'debian') if not os.path.exists(debian_dir): sys.exit("No debian directory found at '{0}', cannot process templates." .format(debian_dir)) return __process_template_folder(debian_dir, subs) def match_branches_with_prefix(prefix, get_branches, prune=False): debug("match_branches_with_prefix(" + str(prefix) + ", " + str(get_branches()) + ")") branches = [] # Match branches existing_branches = get_branches() for branch in existing_branches: if branch.startswith('remotes/origin/'): branch = branch.split('/', 2)[-1] if branch.startswith(prefix): branches.append(branch) branches = list(set(branches)) if prune: # Prune listed branches by packages in latest upstream with inbranch('upstream'): pkg_names, version, pkgs_dict = get_package_data('upstream') for branch in branches: if branch.split(prefix)[-1].strip('/') not in pkg_names: branches.remove(branch) return branches def get_package_from_branch(branch): with inbranch(branch): try: package_data = get_package_data(branch) except SystemExit: return None if type(package_data) not in [list, tuple]: # It is a ret code DebianGenerator.exit(package_data) names, version, packages = package_data if type(names) is list and len(names) > 1: DebianGenerator.exit( "Debian generator does not support generating " "from branches with multiple packages in them, use " "the release generator first to split packages into " "individual branches.") if type(packages) is dict: return list(packages.values())[0] def debianize_string(value): markup_remover = re.compile(r'<.*?>') value = markup_remover.sub('', value) value = re.sub('\s+', ' ', value) value = value.strip() return value def sanitize_package_name(name): return name.replace('_', '-') class DebianGenerator(BloomGenerator): title = 'debian' description = "Generates debians from the catkin meta data" has_run_rosdep = os.environ.get('BLOOM_SKIP_ROSDEP_UPDATE', '0').lower() not in ['0', 'f', 'false', 'n', 'no'] default_install_prefix = '/usr' rosdistro = os.environ.get('ROS_DISTRO', 'indigo') def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('-i', '--debian-inc', help="debian increment number", default='0') add('-p', '--prefix', required=True, help="branch prefix to match, and from which create debians" " hint: if you want to match 'release/foo' use 'release'") add('-a', '--match-all', default=False, action="store_true", help="match all branches with the given prefix, " "even if not in current upstream") add('--distros', nargs='+', required=False, default=[], help='A list of debian (ubuntu) distros to generate for') add('--install-prefix', default=None, help="overrides the default installation prefix (/usr)") add('--os-name', default='ubuntu', help="overrides os_name, set to 'ubuntu' by default") add('--os-not-required', default=False, action="store_true", help="Do not error if this os is not in the platforms " "list for rosdistro") def handle_arguments(self, args): self.interactive = args.interactive self.debian_inc = args.debian_inc self.os_name = args.os_name self.distros = args.distros if self.distros in [None, []]: index = rosdistro.get_index(rosdistro.get_index_url()) distribution_file = rosdistro.get_distribution_file(index, self.rosdistro) if self.os_name not in distribution_file.release_platforms: if args.os_not_required: warning("No platforms defined for os '{0}' in release file for the " "'{1}' distro. This os was not required; continuing without error." .format(self.os_name, self.rosdistro)) sys.exit(0) error("No platforms defined for os '{0}' in release file for the '{1}' distro." .format(self.os_name, self.rosdistro), exit=True) self.distros = distribution_file.release_platforms[self.os_name] self.install_prefix = args.install_prefix if args.install_prefix is None: self.install_prefix = self.default_install_prefix self.prefix = args.prefix self.branches = match_branches_with_prefix(self.prefix, get_branches, prune=not args.match_all) if len(self.branches) == 0: error( "No packages found, check your --prefix or --src arguments.", exit=True ) self.packages = {} self.tag_names = {} self.names = [] self.branch_args = [] self.debian_branches = [] for branch in self.branches: package = get_package_from_branch(branch) if package is None: # This is an ignored package continue self.packages[package.name] = package self.names.append(package.name) args = self.generate_branching_arguments(package, branch) # First branch is debian/[/] self.debian_branches.append(args[0][0]) self.branch_args.extend(args) def summarize(self): info("Generating source debs for the packages: " + str(self.names)) info("Debian Incremental Version: " + str(self.debian_inc)) info("Debian Distributions: " + str(self.distros)) def get_branching_arguments(self): return self.branch_args def update_rosdep(self): update_rosdep() self.has_run_rosdep = True def _check_all_keys_are_valid(self, peer_packages, ros_distro): keys_to_resolve = [] key_to_packages_which_depends_on = collections.defaultdict(list) keys_to_ignore = set() for package in self.packages.values(): evaluate_package_conditions(package, ros_distro) depends = [ dep for dep in (package.run_depends + package.buildtool_export_depends) if dep.evaluated_condition is not False] build_depends = [ dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends) if dep.evaluated_condition is not False] unresolved_keys = [ dep for dep in (depends + build_depends + package.replaces + package.conflicts) if dep.evaluated_condition is not False] keys_to_ignore = { dep for dep in keys_to_ignore.union(package.replaces + package.conflicts) if dep.evaluated_condition is not False} keys = [d.name for d in unresolved_keys] keys_to_resolve.extend(keys) for key in keys: key_to_packages_which_depends_on[key].append(package.name) os_name = self.os_name rosdistro = self.rosdistro all_keys_valid = True for key in sorted(set(keys_to_resolve)): for os_version in self.distros: try: extended_peer_packages = peer_packages + [d.name for d in keys_to_ignore] rule, installer_key, default_installer_key = \ resolve_rosdep_key(key, os_name, os_version, rosdistro, extended_peer_packages, retry=False) if rule is None: continue if installer_key != default_installer_key: error("Key '{0}' resolved to '{1}' with installer '{2}', " "which does not match the default installer '{3}'." .format(key, rule, installer_key, default_installer_key)) BloomGenerator.exit( "The Debian generator does not support dependencies " "which are installed with the '{0}' installer." .format(installer_key), returncode=code.GENERATOR_INVALID_INSTALLER_KEY) except (GeneratorError, RuntimeError) as e: print(fmt("Failed to resolve @{cf}@!{key}@| on @{bf}{os_name}@|:@{cf}@!{os_version}@| with: {e}") .format(**locals())) print(fmt("@{cf}@!{0}@| is depended on by these packages: ").format(key) + str(list(set(key_to_packages_which_depends_on[key])))) print(fmt("@{kf}@!<== @{rf}@!Failed@|")) all_keys_valid = False return all_keys_valid def pre_modify(self): info("\nPre-verifying Debian dependency keys...") # Run rosdep update is needed if not self.has_run_rosdep: self.update_rosdep() peer_packages = [p.name for p in self.packages.values()] while not self._check_all_keys_are_valid(peer_packages, self.rosdistro): error("Some of the dependencies for packages in this repository could not be resolved by rosdep.") if not self.interactive: sys.exit(code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO) error("You can try to address the issues which appear above and try again if you wish.") try: if not maybe_continue(msg="Would you like to try again?"): error("User aborted after rosdep keys were not resolved.") sys.exit(code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO) except (KeyboardInterrupt, EOFError): error("\nUser quit.", exit=True) update_rosdep() invalidate_view_cache() info("All keys are " + ansi('greenf') + "OK" + ansi('reset') + "\n") def pre_branch(self, destination, source): if destination in self.debian_branches: return # Run rosdep update is needed if not self.has_run_rosdep: self.update_rosdep() # Determine the current package being generated name = destination.split('/')[-1] distro = destination.split('/')[-2] # Retrieve the package package = self.packages[name] # Report on this package self.summarize_package(package, distro) def pre_rebase(self, destination): # Get the stored configs is any patches_branch = 'patches/' + destination config = self.load_original_config(patches_branch) if config is not None: curr_config = get_patch_config(patches_branch) if curr_config['parent'] == config['parent']: set_patch_config(patches_branch, config) def post_rebase(self, destination): name = destination.split('/')[-1] # Retrieve the package package = self.packages[name] # Handle differently if this is a debian vs distro branch if destination in self.debian_branches: info("Placing debian template files into '{0}' branch." .format(destination)) # Then this is a debian branch # Place the raw template files self.place_template_files(package.get_build_type()) else: # This is a distro specific debian branch # Determine the current package being generated distro = destination.split('/')[-2] # Create debians for each distro with inbranch(destination): data = self.generate_debian(package, distro) # Create the tag name for later self.tag_names[destination] = self.generate_tag_name(data) # Update the patch configs patches_branch = 'patches/' + destination config = get_patch_config(patches_branch) # Store it self.store_original_config(config, patches_branch) # Modify the base so import/export patch works current_branch = get_current_branch() if current_branch is None: error("Could not determine current branch.", exit=True) config['base'] = get_commit_hash(current_branch) # Set it set_patch_config(patches_branch, config) def post_patch(self, destination, color='bluef'): if destination in self.debian_branches: return # Tag after patches have been applied with inbranch(destination): # Tag tag_name = self.tag_names[destination] if tag_exists(tag_name): if self.interactive: warning("Tag exists: " + tag_name) warning("Do you wish to overwrite it?") if not maybe_continue('y'): error("Answered no to continue, aborting.", exit=True) else: warning("Overwriting tag: " + tag_name) else: info("Creating tag: " + tag_name) execute_command('git tag -f ' + tag_name) # Report of success name = destination.split('/')[-1] package = self.packages[name] distro = destination.split('/')[-2] info(ansi(color) + "####" + ansi('reset'), use_prefix=False) info( ansi(color) + "#### " + ansi('greenf') + "Successfully" + ansi(color) + " generated '" + ansi('boldon') + distro + ansi('boldoff') + "' debian for package" " '" + ansi('boldon') + package.name + ansi('boldoff') + "'" + " at version '" + ansi('boldon') + package.version + "-" + str(self.debian_inc) + ansi('boldoff') + "'" + ansi('reset'), use_prefix=False ) info(ansi(color) + "####\n" + ansi('reset'), use_prefix=False) def store_original_config(self, config, patches_branch): with inbranch(patches_branch): with open('debian.store', 'w+') as f: f.write(json.dumps(config)) execute_command('git add debian.store') if has_changes(): execute_command('git commit -m "Store original patch config"') def load_original_config(self, patches_branch): config_store = show(patches_branch, 'debian.store') if config_store is None: return config_store return json.loads(config_store) def place_template_files(self, build_type, debian_dir='debian'): # Create/Clean the debian folder if os.path.exists(debian_dir): if self.interactive: warning("debian directory exists: " + debian_dir) warning("Do you wish to overwrite it?") if not maybe_continue('y'): error("Answered no to continue, aborting.", exit=True) elif 'BLOOM_CLEAR_DEBIAN_ON_GENERATION' in os.environ: warning("Overwriting debian directory: " + debian_dir) execute_command('git rm -rf ' + debian_dir) execute_command('git commit -m "Clearing previous debian folder"') if os.path.exists(debian_dir): shutil.rmtree(debian_dir) else: warning("Not overwriting debian directory.") # Use generic place template files command place_template_files('.', build_type, gbp=True) # Commit results execute_command('git add ' + debian_dir) _, has_files, _ = execute_command('git diff --cached --name-only', return_io=True) if has_files: execute_command('git commit -m "Placing debian template files"') def get_releaser_history(self): # Assumes that this is called in the target branch patches_branch = 'patches/' + get_current_branch() raw = show(patches_branch, 'releaser_history.json') return None if raw is None else json.loads(raw) def set_releaser_history(self, history): # Assumes that this is called in the target branch patches_branch = 'patches/' + get_current_branch() debug("Writing release history to '{0}' branch".format(patches_branch)) with inbranch(patches_branch): with open('releaser_history.json', 'w') as f: f.write(json.dumps(history)) execute_command('git add releaser_history.json') if has_changes(): execute_command('git commit -m "Store releaser history"') def get_subs(self, package, debian_distro, releaser_history=None): return generate_substitutions_from_package( package, self.os_name, debian_distro, self.rosdistro, self.install_prefix, self.debian_inc, [p.name for p in self.packages.values()], releaser_history=releaser_history, fallback_resolver=missing_dep_resolver ) def generate_debian(self, package, debian_distro): info("Generating debian for {0}...".format(debian_distro)) # Try to retrieve the releaser_history releaser_history = self.get_releaser_history() # Generate substitution values subs = self.get_subs(package, debian_distro, releaser_history) # Use subs to create and store releaser history releaser_history = [(v, (n, e)) for v, _, _, n, e in subs['changelogs']] self.set_releaser_history(dict(releaser_history)) # Handle gbp.conf subs['release_tag'] = self.get_release_tag(subs) # Template files template_files = process_template_files('.', subs) # Remove any residual template files execute_command('git rm -rf ' + ' '.join("'{}'".format(t) for t in template_files)) # Add changes to the debian folder execute_command('git add debian') # Commit changes execute_command('git commit -m "Generated debian files for ' + debian_distro + '"') # Return the subs for other use return subs def get_release_tag(self, data): return 'release/{0}/{1}-{2}'.format(data['Name'], data['Version'], self.debian_inc) def generate_tag_name(self, data): tag_name = '{Package}_{Version}{DebianInc}_{Distribution}' tag_name = 'debian/' + tag_name.format(**data) return tag_name def generate_branching_arguments(self, package, branch): n = package.name # Debian branch deb_branch = 'debian/' + n # Branch first to the debian branch args = [[deb_branch, branch, False]] # Then for each debian distro, branch from the base debian branch args.extend([ ['debian/' + d + '/' + n, deb_branch, False] for d in self.distros ]) return args def summarize_package(self, package, distro, color='bluef'): info(ansi(color) + "\n####" + ansi('reset'), use_prefix=False) info( ansi(color) + "#### Generating '" + ansi('boldon') + distro + ansi('boldoff') + "' debian for package" " '" + ansi('boldon') + package.name + ansi('boldoff') + "'" + " at version '" + ansi('boldon') + package.version + "-" + str(self.debian_inc) + ansi('boldoff') + "'" + ansi('reset'), use_prefix=False ) info(ansi(color) + "####" + ansi('reset'), use_prefix=False) bloom-0.10.7/bloom/generators/debian/templates/000077500000000000000000000000001403641720100214065ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/000077500000000000000000000000001403641720100236525ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/changelog.em000066400000000000000000000003661403641720100261310ustar00rootroot00000000000000@[for change_version, change_date, changelog, main_name, main_email in changelogs]@(Package) (@(change_version)@(DebianInc)@(Distribution)) @(Distribution); urgency=high @(changelog) -- @(main_name) <@(main_email)> @(change_date) @[end for] bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/compat.em000066400000000000000000000000251403641720100254550ustar00rootroot00000000000000@(debhelper_version) bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/control.em000066400000000000000000000007211403641720100256550ustar00rootroot00000000000000Source: @(Package) Section: misc Priority: optional Maintainer: @(Maintainer) Build-Depends: debhelper (>= @(debhelper_version).0.0), @(', '.join(BuildDepends)) Homepage: @(Homepage) Standards-Version: 3.9.2 Package: @(Package) Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, @(', '.join(Depends)) @[if Conflicts]Conflicts: @(', '.join(Conflicts))@\n@[end if]@ @[if Replaces]Replaces: @(', '.join(Replaces))@\n@[end if]@ Description: @(Description) bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/copyright.em000066400000000000000000000000141403641720100262000ustar00rootroot00000000000000@(Copyright)bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/gbp.conf.em000066400000000000000000000001011403641720100256610ustar00rootroot00000000000000[git-buildpackage] upstream-tag=@(release_tag) upstream-tree=tag bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/rules.em000077500000000000000000000053531403641720100253400ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 # TODO: remove the LDFLAGS override. It's here to avoid esoteric problems # of this sort: # https://code.ros.org/trac/ros/ticket/2977 # https://code.ros.org/trac/ros/ticket/3842 export LDFLAGS= export PKG_CONFIG_PATH=@(InstallationPrefix)/lib/pkgconfig # Explicitly enable -DNDEBUG, see: # https://github.com/ros-infrastructure/bloom/issues/327 export DEB_CXXFLAGS_MAINT_APPEND=-DNDEBUG %: dh $@@ -v --buildsystem=cmake override_dh_auto_configure: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_configure -- \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DAMENT_PREFIX_PATH="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" override_dh_auto_build: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_build override_dh_auto_test: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. echo -- Running tests. Even if one of them fails the build is not canceled. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_test || true override_dh_shlibdeps: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_shlibdeps -l$(CURDIR)/debian/@(Package)/@(InstallationPrefix)/lib/ override_dh_auto_install: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_install bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/source/000077500000000000000000000000001403641720100251525ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/source/format.em000066400000000000000000000000201403641720100267550ustar00rootroot000000000000003.0 (@(format)) bloom-0.10.7/bloom/generators/debian/templates/ament_cmake/source/options.em000066400000000000000000000003711403641720100271710ustar00rootroot00000000000000@[if format and format == 'quilt']@ # Automatically add upstream changes to the quilt overlay. # http://manpages.ubuntu.com/manpages/trusty/man1/dpkg-source.1.html # This supports reusing the orig.tar.gz for debian increments. auto-commit @[end if] bloom-0.10.7/bloom/generators/debian/templates/ament_python/000077500000000000000000000000001403641720100241135ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/ament_python/changelog.em000066400000000000000000000003661403641720100263720ustar00rootroot00000000000000@[for change_version, change_date, changelog, main_name, main_email in changelogs]@(Package) (@(change_version)@(DebianInc)@(Distribution)) @(Distribution); urgency=high @(changelog) -- @(main_name) <@(main_email)> @(change_date) @[end for] bloom-0.10.7/bloom/generators/debian/templates/ament_python/compat.em000066400000000000000000000000251403641720100257160ustar00rootroot00000000000000@(debhelper_version) bloom-0.10.7/bloom/generators/debian/templates/ament_python/control.em000066400000000000000000000007631403641720100261240ustar00rootroot00000000000000Source: @(Package) Section: misc Priority: optional Maintainer: @(Maintainer) Build-Depends: debhelper (>= @(debhelper_version).0.0), @(', '.join(BuildDepends)), python3-all, python3-setuptools Homepage: @(Homepage) Standards-Version: 3.9.2 Package: @(Package) Architecture: any Depends: ${python3:Depends}, ${misc:Depends}, @(', '.join(Depends)) @[if Conflicts]Conflicts: @(', '.join(Conflicts))@\n@[end if]@ @[if Replaces]Replaces: @(', '.join(Replaces))@\n@[end if]@ Description: @(Description) bloom-0.10.7/bloom/generators/debian/templates/ament_python/copyright.em000066400000000000000000000000141403641720100264410ustar00rootroot00000000000000@(Copyright)bloom-0.10.7/bloom/generators/debian/templates/ament_python/gbp.conf.em000066400000000000000000000001011403641720100261220ustar00rootroot00000000000000[git-buildpackage] upstream-tag=@(release_tag) upstream-tree=tag bloom-0.10.7/bloom/generators/debian/templates/ament_python/rules.em000077500000000000000000000055211403641720100255760ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 # TODO: remove the LDFLAGS override. It's here to avoid esoteric problems # of this sort: # https://code.ros.org/trac/ros/ticket/2977 # https://code.ros.org/trac/ros/ticket/3842 export LDFLAGS= export PKG_CONFIG_PATH=@(InstallationPrefix)/lib/pkgconfig # Explicitly enable -DNDEBUG, see: # https://github.com/ros-infrastructure/bloom/issues/327 export DEB_CXXFLAGS_MAINT_APPEND=-DNDEBUG # Python package installation variables export PYBUILD_INSTALL_ARGS=--prefix "@(InstallationPrefix)" \ --install-lib "\$$base/lib/{interpreter}/site-packages" \ @[ if pass_install_scripts ] --install-scripts "\$$base/bin" @[end if] %: dh $@@ -v --buildsystem=pybuild --with python3 override_dh_auto_configure: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_configure override_dh_auto_build: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_build override_dh_auto_test: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. echo -- Running tests. Even if one of them fails the build is not canceled. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_test || true override_dh_shlibdeps: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_shlibdeps -l$(CURDIR)/debian/@(Package)/@(InstallationPrefix)/lib/ override_dh_auto_install: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_install bloom-0.10.7/bloom/generators/debian/templates/ament_python/source/000077500000000000000000000000001403641720100254135ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/ament_python/source/format.em000066400000000000000000000000201403641720100272160ustar00rootroot000000000000003.0 (@(format)) bloom-0.10.7/bloom/generators/debian/templates/ament_python/source/options.em000066400000000000000000000003711403641720100274320ustar00rootroot00000000000000@[if format and format == 'quilt']@ # Automatically add upstream changes to the quilt overlay. # http://manpages.ubuntu.com/manpages/trusty/man1/dpkg-source.1.html # This supports reusing the orig.tar.gz for debian increments. auto-commit @[end if] bloom-0.10.7/bloom/generators/debian/templates/catkin/000077500000000000000000000000001403641720100226575ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/catkin/changelog.em000066400000000000000000000003661403641720100251360ustar00rootroot00000000000000@[for change_version, change_date, changelog, main_name, main_email in changelogs]@(Package) (@(change_version)@(DebianInc)@(Distribution)) @(Distribution); urgency=high @(changelog) -- @(main_name) <@(main_email)> @(change_date) @[end for] bloom-0.10.7/bloom/generators/debian/templates/catkin/compat.em000066400000000000000000000000251403641720100244620ustar00rootroot00000000000000@(debhelper_version) bloom-0.10.7/bloom/generators/debian/templates/catkin/control.em000066400000000000000000000007211403641720100246620ustar00rootroot00000000000000Source: @(Package) Section: misc Priority: optional Maintainer: @(Maintainer) Build-Depends: debhelper (>= @(debhelper_version).0.0), @(', '.join(BuildDepends)) Homepage: @(Homepage) Standards-Version: 3.9.2 Package: @(Package) Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, @(', '.join(Depends)) @[if Conflicts]Conflicts: @(', '.join(Conflicts))@\n@[end if]@ @[if Replaces]Replaces: @(', '.join(Replaces))@\n@[end if]@ Description: @(Description) bloom-0.10.7/bloom/generators/debian/templates/catkin/copyright.em000066400000000000000000000000141403641720100252050ustar00rootroot00000000000000@(Copyright)bloom-0.10.7/bloom/generators/debian/templates/catkin/gbp.conf.em000066400000000000000000000001011403641720100246660ustar00rootroot00000000000000[git-buildpackage] upstream-tag=@(release_tag) upstream-tree=tag bloom-0.10.7/bloom/generators/debian/templates/catkin/rules.em000077500000000000000000000055551403641720100243510ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 # TODO: remove the LDFLAGS override. It's here to avoid esoteric problems # of this sort: # https://code.ros.org/trac/ros/ticket/2977 # https://code.ros.org/trac/ros/ticket/3842 export LDFLAGS= export PKG_CONFIG_PATH=@(InstallationPrefix)/lib/pkgconfig # Explicitly enable -DNDEBUG, see: # https://github.com/ros-infrastructure/bloom/issues/327 export DEB_CXXFLAGS_MAINT_APPEND=-DNDEBUG %: dh $@@ -v --buildsystem=cmake override_dh_auto_configure: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_configure -- \ -DCATKIN_BUILD_BINARY_PACKAGE="1" \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" override_dh_auto_build: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_build override_dh_auto_test: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. echo -- Running tests. Even if one of them fails the build is not canceled. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_test || true override_dh_shlibdeps: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_shlibdeps -l$(CURDIR)/debian/@(Package)/@(InstallationPrefix)/lib/ override_dh_auto_install: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_install bloom-0.10.7/bloom/generators/debian/templates/catkin/source/000077500000000000000000000000001403641720100241575ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/catkin/source/format.em000066400000000000000000000000201403641720100257620ustar00rootroot000000000000003.0 (@(format)) bloom-0.10.7/bloom/generators/debian/templates/catkin/source/options.em000066400000000000000000000003711403641720100261760ustar00rootroot00000000000000@[if format and format == 'quilt']@ # Automatically add upstream changes to the quilt overlay. # http://manpages.ubuntu.com/manpages/trusty/man1/dpkg-source.1.html # This supports reusing the orig.tar.gz for debian increments. auto-commit @[end if] bloom-0.10.7/bloom/generators/debian/templates/cmake/000077500000000000000000000000001403641720100224665ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/cmake/changelog.em000066400000000000000000000003661403641720100247450ustar00rootroot00000000000000@[for change_version, change_date, changelog, main_name, main_email in changelogs]@(Package) (@(change_version)@(DebianInc)@(Distribution)) @(Distribution); urgency=high @(changelog) -- @(main_name) <@(main_email)> @(change_date) @[end for] bloom-0.10.7/bloom/generators/debian/templates/cmake/compat.em000066400000000000000000000000251403641720100242710ustar00rootroot00000000000000@(debhelper_version) bloom-0.10.7/bloom/generators/debian/templates/cmake/control.em000066400000000000000000000007211403641720100244710ustar00rootroot00000000000000Source: @(Package) Section: misc Priority: optional Maintainer: @(Maintainer) Build-Depends: debhelper (>= @(debhelper_version).0.0), @(', '.join(BuildDepends)) Homepage: @(Homepage) Standards-Version: 3.9.2 Package: @(Package) Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, @(', '.join(Depends)) @[if Conflicts]Conflicts: @(', '.join(Conflicts))@\n@[end if]@ @[if Replaces]Replaces: @(', '.join(Replaces))@\n@[end if]@ Description: @(Description) bloom-0.10.7/bloom/generators/debian/templates/cmake/copyright.em000066400000000000000000000000141403641720100250140ustar00rootroot00000000000000@(Copyright)bloom-0.10.7/bloom/generators/debian/templates/cmake/gbp.conf.em000066400000000000000000000001011403641720100244750ustar00rootroot00000000000000[git-buildpackage] upstream-tag=@(release_tag) upstream-tree=tag bloom-0.10.7/bloom/generators/debian/templates/cmake/rules.em000077500000000000000000000056321403641720100241540ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 # TODO: remove the LDFLAGS override. It's here to avoid esoteric problems # of this sort: # https://code.ros.org/trac/ros/ticket/2977 # https://code.ros.org/trac/ros/ticket/3842 export LDFLAGS= export PKG_CONFIG_PATH=@(InstallationPrefix)/lib/pkgconfig # Explicitly enable -DNDEBUG, see: # https://github.com/ros-infrastructure/bloom/issues/327 export DEB_CXXFLAGS_MAINT_APPEND=-DNDEBUG # Solve shlibdeps errors in REP136 packages that use GNUInstallDirs: export DEB_HOST_MULTIARCH := $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@@ -v --buildsystem=cmake override_dh_auto_configure: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_configure -- \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" override_dh_auto_build: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_build override_dh_auto_test: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. echo -- Running tests. Even if one of them fails the build is not canceled. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_test || true override_dh_shlibdeps: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_shlibdeps -l$(CURDIR)/debian/@(Package)/@(InstallationPrefix)/lib/:$(CURDIR)/debian/@(Package)/@(InstallationPrefix)/lib/${DEB_HOST_MULTIARCH} override_dh_auto_install: # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi && \ dh_auto_install bloom-0.10.7/bloom/generators/debian/templates/cmake/source/000077500000000000000000000000001403641720100237665ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/debian/templates/cmake/source/format.em000066400000000000000000000000201403641720100255710ustar00rootroot000000000000003.0 (@(format)) bloom-0.10.7/bloom/generators/debian/templates/cmake/source/options.em000066400000000000000000000003711403641720100260050ustar00rootroot00000000000000@[if format and format == 'quilt']@ # Automatically add upstream changes to the quilt overlay. # http://manpages.ubuntu.com/manpages/trusty/man1/dpkg-source.1.html # This supports reusing the orig.tar.gz for debian increments. auto-commit @[end if] bloom-0.10.7/bloom/generators/release.py000077500000000000000000000160301403641720100201630ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import traceback from bloom.generators import BloomGenerator from bloom.git import inbranch from bloom.git import get_current_branch from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import warning from bloom.packages import get_package_data from bloom.util import execute_command from bloom.commands.git.patch.trim_cmd import trim try: import catkin_pkg from pkg_resources import parse_version if parse_version(catkin_pkg.__version__) < parse_version('0.3.8'): warning("This version of bloom requires catkin_pkg version >= '0.3.8'," " the used version of catkin_pkg is '{0}'".format(catkin_pkg.__version__)) from catkin_pkg import metapackage except ImportError as err: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", exit=True) class ReleaseGenerator(BloomGenerator): title = 'release' description = """\ Generates a release branch for each of the packages in the source branch. The common use case for this generator is to produce release/* branches for each package in the upstream repository, so the source branch should be set to 'upstream' and the prefix set to 'release'. """ def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('-s', '--src', '--source-branch', default=None, dest='src', help="git branch to branch from (defaults to 'upstream')") add('-n', '--package-name', default=None, dest='name', help="name of package being released (use if non catkin project)") add('-p', '--prefix', default='release', dest='prefix', help="prefix for target branch name(s)") add('--release-increment', '-i', default=0, help="release increment number") return BloomGenerator.prepare_arguments(self, parser) def handle_arguments(self, args): self.interactive = args.interactive self.prefix = args.prefix if args.src is None: current_branch = get_current_branch() if current_branch is None: error("Could not determine current branch.", exit=True) self.src = current_branch else: self.src = args.src self.name = args.name self.release_inc = args.release_increment def summarize(self): self.branch_list = self.detect_branches() if type(self.branch_list) not in [list, tuple]: self.exit(self.branch_list if self.branch_list is not None else 1) info("Releasing package" + ('' if len(self.branch_list) == 1 else 's') + ": " + str(self.branch_list)) def get_branching_arguments(self): p, s, i = self.prefix, self.src, self.interactive self.branch_args = [['/'.join([p, b]), s, i] for b in self.branch_list] return self.branch_args def pre_rebase(self, destination, msg=None): name = destination.split('/')[-1] msg = msg if msg is not None else ( "Releasing package '" + name + "' to: '" + destination + "'" ) info(msg) ret = trim(undo=True) return 0 if ret is None or ret < 0 else ret # Ret < 0 indicates nothing was done def post_rebase(self, destination): # Figure out the trim sub dir name = destination.split('/')[-1] trim_d = [k for k, v in self.packages.items() if v.name == name][0] # Execute trim if trim_d in ['', '.']: return return trim(trim_d) def post_patch(self, destination): # Figure out the version of the given package if self.name is not None: warning("""\ Cannot automatically tag the release because this is not a catkin project.""") warning("""\ Please checkout the release branch and then create a tag manually with:""") warning(" git checkout release/" + str(self.name)) warning(" git tag -f release/" + str(self.name) + "/") return with inbranch(destination): name, version, packages = get_package_data(destination) # Execute git tag release_tag = destination + '/' + version + '-' + self.release_inc execute_command('git tag ' + release_tag) def metapackage_check(self, path, pkg): if pkg.is_metapackage(): try: metapackage.validate_metapackage(path, pkg) except metapackage.InvalidMetapackage as e: warning("Invalid metapackage:") warning(" %s\n" % str(e)) error(fmt("Refusing to release invalid metapackage '@|%s@{rf}@!', metapackage requirements:\n @|%s" % (pkg.name, metapackage.DEFINITION_URL)), exit=True) def detect_branches(self): self.packages = None with inbranch(self.src): if self.name is not None: self.packages = [self.name] return [self.name] name, version, packages = get_package_data(self.src) self.packages = packages # Check meta packages for valid CMakeLists.txt if isinstance(self.packages, dict): for path, pkg in self.packages.items(): # Check for valid CMakeLists.txt if a metapackage self.metapackage_check(path, pkg) return name if type(name) is list else [name] bloom-0.10.7/bloom/generators/rosdebian.py000066400000000000000000000167701403641720100205210ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from bloom.generators.common import default_fallback_resolver from bloom.generators.debian.generator import sanitize_package_name from bloom.generators.debian import DebianGenerator from bloom.generators.debian.generator import generate_substitutions_from_package from bloom.generators.debian.generate_cmd import main as debian_main from bloom.generators.debian.generate_cmd import prepare_arguments from bloom.logging import info from bloom.rosdistro_api import get_index from bloom.util import get_distro_list_prompt class RosDebianGenerator(DebianGenerator): title = 'rosdebian' description = "Generates debians tailored for the given rosdistro" default_install_prefix = '/opt/ros/' def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('rosdistro', help="ROS distro to target (%s, etc.)" % get_distro_list_prompt()) return DebianGenerator.prepare_arguments(self, parser) def handle_arguments(self, args): self.rosdistro = args.rosdistro self.default_install_prefix += self.rosdistro ret = DebianGenerator.handle_arguments(self, args) return ret def summarize(self): ret = DebianGenerator.summarize(self) info("Releasing for rosdistro: " + self.rosdistro) return ret def get_subs(self, package, debian_distro, releaser_history, deb_inc=0, native=False): def fallback_resolver(key, peer_packages, rosdistro=self.rosdistro): if key in peer_packages: return [sanitize_package_name(rosify_package_name(key, rosdistro))] return default_fallback_resolver(key, peer_packages) subs = generate_substitutions_from_package( package, self.os_name, debian_distro, self.rosdistro, self.install_prefix, self.debian_inc, [p.name for p in self.packages.values()], releaser_history=releaser_history, fallback_resolver=fallback_resolver ) subs['Rosdistro'] = self.rosdistro subs['Package'] = rosify_package_name(subs['Package'], self.rosdistro) # ROS 2 specific bloom extensions. ros2_distros = [ name for name, values in get_index().distributions.items() if values.get('distribution_type') == 'ros2'] if self.rosdistro in ros2_distros: # Add ros-workspace package as a dependency to any package other # than ros_workspace and its dependencies. if package.name not in ['ament_cmake_core', 'ament_package', 'ros_workspace']: workspace_pkg_name = rosify_package_name('ros-workspace', self.rosdistro) subs['BuildDepends'].append(workspace_pkg_name) subs['Depends'].append(workspace_pkg_name) # Add packages necessary to build vendor typesupport for rosidl_interface_packages to their # build dependencies. if self.rosdistro in ros2_distros and \ self.rosdistro not in ('r2b2', 'r2b3', 'ardent') and \ 'rosidl_interface_packages' in [p.name for p in package.member_of_groups]: ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES = [ 'rosidl-typesupport-fastrtps-c', 'rosidl-typesupport-fastrtps-cpp', ] # Connext was changed to a new rmw that doesn't require typesupport after Foxy if self.rosdistro in ('bouncy', 'crystal', 'dashing', 'eloquent', 'foxy'): ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES.extend([ 'rosidl-typesupport-connext-c', 'rosidl-typesupport-connext-cpp', ]) # OpenSplice was dropped after Eloquent. # rmw implementations are required as dependencies up to Eloquent. if self.rosdistro in ('bouncy', 'crystal', 'dashing', 'eloquent'): ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES.extend([ 'rmw-connext-cpp', 'rmw-fastrtps-cpp', 'rmw-implementation', 'rmw-opensplice-cpp', 'rosidl-typesupport-opensplice-c', 'rosidl-typesupport-opensplice-cpp', ]) subs['BuildDepends'] += [ rosify_package_name(name, self.rosdistro) for name in ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES] return subs def generate_branching_arguments(self, package, branch): deb_branch = 'debian/' + self.rosdistro + '/' + package.name args = [[deb_branch, branch, False]] n, r, b, ds = package.name, self.rosdistro, deb_branch, self.distros args.extend([ ['debian/' + r + '/' + d + '/' + n, b, False] for d in ds ]) return args def get_release_tag(self, data): return 'release/{0}/{1}/{2}-{3}'\ .format(self.rosdistro, data['Name'], data['Version'], self.debian_inc) def rosify_package_name(name, rosdistro): return 'ros-{0}-{1}'.format(rosdistro, name) def get_subs(pkg, os_name, os_version, ros_distro, deb_inc, native): # No fallback_resolver provided because peer packages not considered. subs = generate_substitutions_from_package( pkg, os_name, os_version, ros_distro, RosDebianGenerator.default_install_prefix + ros_distro, deb_inc=deb_inc, native=native ) subs['Package'] = rosify_package_name(subs['Package'], ros_distro) return subs def main(args=None): debian_main(args, get_subs) # This describes this command to the loader description = dict( title='rosdebian', description="Generates ROS style debian packaging files for a catkin package", main=main, prepare_arguments=prepare_arguments ) bloom-0.10.7/bloom/generators/rosrelease.py000066400000000000000000000061511403641720100207070ustar00rootroot00000000000000from __future__ import print_function from bloom.generators.release import ReleaseGenerator from bloom.git import inbranch from bloom.logging import warning from bloom.packages import get_package_data from bloom.util import execute_command from bloom.util import get_distro_list_prompt class RosReleaseGenerator(ReleaseGenerator): title = 'rosrelease' description = """\ Generates a release branch for each of the packages in the source branch. The common use case for this generator is to produce a release// branch for each package in the upstream repository, so the source branch should be set to 'upstream' and the prefix set to 'release'. """ def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('rosdistro', help="ROS distro to target (%s, etc.)" % get_distro_list_prompt()) return ReleaseGenerator.prepare_arguments(self, parser) def handle_arguments(self, args): self.rosdistro = args.rosdistro return ReleaseGenerator.handle_arguments(self, args) def get_branching_arguments(self): p, d, s, i = self.prefix, self.rosdistro, self.src, self.interactive self.branch_args = [ ['/'.join([p, d, b]), s, i] for b in self.branch_list ] return self.branch_args def pre_rebase(self, destination): name = destination.split('/')[-1] return ReleaseGenerator.pre_rebase( self, destination, "Releasing package '{0}' for '{1}' to: '{2}'".format( name, self.rosdistro, destination ) ) def post_patch(self, destination): # Figure out the version of the given package if self.name is not None: warning("""\ Cannot automatically tag the release because this is not a catkin project.""") warning("""\ Please checkout the release branch and then create a tag manually with:""") warning(" git checkout " + destination) warning(" git tag -f " + destination + "/") return with inbranch(destination): name, version, packages = get_package_data(destination) # Execute git tag execute_command('git tag -f ' + destination + '/' + version + '-' + str(self.release_inc)) def detect_branches(self): self.packages = None with inbranch(self.src): if self.name is not None: self.packages = [self.name] return [self.name] package_data = get_package_data(self.src) if type(package_data) not in [list, tuple]: return package_data name, version, packages = package_data self.packages = packages # Check meta packages for valid CMakeLists.txt if isinstance(self.packages, dict): for path, pkg in self.packages.items(): # Check for valid CMakeLists.txt if a metapackage self.metapackage_check(path, pkg) return name if type(name) is list else [name] bloom-0.10.7/bloom/generators/rosrpm.py000066400000000000000000000166741403641720100201000ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from bloom.generators.common import default_fallback_resolver from bloom.generators.rpm.generator import sanitize_package_name from bloom.generators.rpm import RpmGenerator from bloom.generators.rpm.generator import generate_substitutions_from_package from bloom.generators.rpm.generate_cmd import main as rpm_main from bloom.generators.rpm.generate_cmd import prepare_arguments from bloom.logging import info from bloom.rosdistro_api import get_index from bloom.util import get_distro_list_prompt class RosRpmGenerator(RpmGenerator): title = 'rosrpm' description = "Generates RPMs tailored for the given rosdistro" default_install_prefix = '/opt/ros/' def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('rosdistro', help="ROS distro to target (%s, etc.)" % get_distro_list_prompt()) return RpmGenerator.prepare_arguments(self, parser) def handle_arguments(self, args): self.rosdistro = args.rosdistro self.default_install_prefix += self.rosdistro ret = RpmGenerator.handle_arguments(self, args) return ret def summarize(self): ret = RpmGenerator.summarize(self) info("Releasing for rosdistro: " + self.rosdistro) return ret def get_subs(self, package, rpm_distro, releaser_history): def fallback_resolver(key, peer_packages, rosdistro=self.rosdistro): if key in peer_packages: return [sanitize_package_name(rosify_package_name(key, rosdistro))] return default_fallback_resolver(key, peer_packages) subs = generate_substitutions_from_package( package, self.os_name, rpm_distro, self.rosdistro, self.install_prefix, self.rpm_inc, [p.name for p in self.packages.values()], releaser_history=releaser_history, fallback_resolver=fallback_resolver, skip_keys=self.skip_keys ) subs['Rosdistro'] = self.rosdistro subs['Package'] = rosify_package_name(subs['Package'], self.rosdistro) # Virtual packages subs['Provides'] += [ '%%{name}-%s = %%{version}-%%{release}' % subpackage for subpackage in [ 'devel', 'doc', 'runtime']] # Group membership subs['Provides'].extend( sanitize_package_name(rosify_package_name(g.name, self.rosdistro)) + '(member)' for g in package.member_of_groups) subs['Supplements'].extend( sanitize_package_name(rosify_package_name(g.name, self.rosdistro)) + '(all)' for g in package.member_of_groups) # ROS 2 specific bloom extensions. ros2_distros = [ name for name, values in get_index().distributions.items() if values.get('distribution_type') == 'ros2'] if self.rosdistro in ros2_distros: # Add ros-workspace package as a dependency to any package other # than ros_workspace and its dependencies. if package.name not in ['ament_cmake_core', 'ament_package', 'ros_workspace']: workspace_pkg_name = rosify_package_name('ros-workspace', self.rosdistro) subs['BuildDepends'].append(workspace_pkg_name) subs['Depends'].append(workspace_pkg_name) # Add packages necessary to build vendor typesupport for rosidl_interface_packages to their # build dependencies. if self.rosdistro in ros2_distros and \ self.rosdistro not in ('r2b2', 'r2b3', 'ardent') and \ 'rosidl_interface_packages' in [p.name for p in package.member_of_groups]: ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES = [ 'rosidl-typesupport-fastrtps-c', 'rosidl-typesupport-fastrtps-cpp', ] # OpenSplice was dropped after Eloquent. # rmw implementations are required as dependencies up to Eloquent. if self.rosdistro in ['bouncy', 'crystal', 'dashing', 'eloquent']: ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES.extend([ 'rmw-fastrtps-cpp', 'rmw-implementation', 'rmw-opensplice-cpp', 'rosidl-typesupport-opensplice-c', 'rosidl-typesupport-opensplice-cpp', ]) subs['BuildDepends'] += [ rosify_package_name(name, self.rosdistro) for name in ROS2_VENDOR_TYPESUPPORT_DEPENDENCIES] return subs def generate_branching_arguments(self, package, branch): rpm_branch = 'rpm/' + self.rosdistro + '/' + package.name args = [[rpm_branch, branch, False]] n, r, b, ds = package.name, self.rosdistro, rpm_branch, self.distros args.extend([ ['rpm/' + r + '/' + d + '/' + n, b, False] for d in ds ]) return args def get_release_tag(self, data): return 'release/{0}/{1}/{2}-{3}'\ .format(self.rosdistro, data['Name'], data['Version'], self.rpm_inc) def rosify_package_name(name, rosdistro): return 'ros-{0}-{1}'.format(rosdistro, name) def get_subs(pkg, os_name, os_version, ros_distro): # No fallback_resolver provided because peer packages not considered. subs = generate_substitutions_from_package( pkg, os_name, os_version, ros_distro ) subs['Package'] = rosify_package_name(subs['Package'], ros_distro) return subs def main(args=None): rpm_main(args, get_subs) # This describes this command to the loader description = dict( title='rosrpm', description="Generates ROS style RPM packaging files for a catkin package", main=main, prepare_arguments=prepare_arguments ) bloom-0.10.7/bloom/generators/rpm/000077500000000000000000000000001403641720100167645ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/__init__.py000077500000000000000000000002061403641720100210760ustar00rootroot00000000000000from .generator import RpmGenerator from .generator import sanitize_package_name __all__ = ['RpmGenerator', 'sanitize_package_name'] bloom-0.10.7/bloom/generators/rpm/generate_cmd.py000066400000000000000000000133631403641720100217610ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import os import sys import traceback from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.generators.rpm.generator import generate_substitutions_from_package from bloom.generators.rpm.generator import place_template_files from bloom.generators.rpm.generator import process_template_files from bloom.util import get_distro_list_prompt try: from rosdep2 import create_default_installer_context except ImportError: debug(traceback.format_exc()) error("rosdep was not detected, please install it.", exit=True) try: from catkin_pkg.packages import find_packages except ImportError: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", exit=True) def prepare_arguments(parser): add = parser.add_argument add('package_path', nargs='?', help="path to or containing the package.xml of a package") action = parser.add_mutually_exclusive_group(required=False) add = action.add_argument add('--place-template-files', action='store_true', help="places rpm/* template file(s) only") add('--process-template-files', action='store_true', help="processes templates in rpm/* only") add = parser.add_argument add('--os-name', help='OS name, e.g. fedora, rhel') add('--os-version', help='OS version or codename, e.g. heisenbug, santiago') add('--ros-distro', help="ROS distro, e.g. %s (used for rosdep)" % get_distro_list_prompt()) return parser def get_subs(pkg, os_name, os_version, ros_distro): return generate_substitutions_from_package( pkg, os_name, os_version, ros_distro ) def main(args=None, get_subs_fn=None): get_subs_fn = get_subs_fn or get_subs _place_template_files = True _process_template_files = True package_path = os.getcwd() if args is not None: package_path = args.package_path or os.getcwd() _place_template_files = args.place_template_files _process_template_files = args.process_template_files pkgs_dict = find_packages(package_path) if len(pkgs_dict) == 0: sys.exit("No packages found in path: '{0}'".format(package_path)) if len(pkgs_dict) > 1: sys.exit("Multiple packages found, " "this tool only supports one package at a time.") os_data = create_default_installer_context().get_os_name_and_version() os_name, os_version = os_data ros_distro = os.environ.get('ROS_DISTRO', 'indigo') # Allow args overrides os_name = args.os_name or os_name os_version = args.os_version or os_version ros_distro = args.ros_distro or ros_distro # Summarize info(fmt("@!@{gf}==> @|") + fmt("Generating RPMs for @{cf}%s:%s@| for package(s) %s" % (os_name, os_version, [p.name for p in pkgs_dict.values()]))) for path, pkg in pkgs_dict.items(): template_files = None try: subs = get_subs_fn(pkg, os_name, os_version, ros_distro) if _place_template_files: # Place template files place_template_files(path, pkg.get_build_type()) if _process_template_files: # Just process existing template files template_files = process_template_files(path, subs) if not _place_template_files and not _process_template_files: # If neither, do both place_template_files(path, pkg.get_build_type()) template_files = process_template_files(path, subs) if template_files is not None: for template_file in template_files: os.remove(os.path.normpath(template_file)) except Exception as exc: debug(traceback.format_exc()) error(type(exc).__name__ + ": " + str(exc), exit=True) except (KeyboardInterrupt, EOFError): sys.exit(1) # This describes this command to the loader description = dict( title='rpm', description="Generates RPM packaging files for a catkin package", main=main, prepare_arguments=prepare_arguments ) bloom-0.10.7/bloom/generators/rpm/generator.py000066400000000000000000001037621403641720100213350ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import collections import datetime import io import json import os import pkg_resources import re import shutil import sys import traceback import textwrap # Python 2/3 support. try: from configparser import SafeConfigParser except ImportError: from ConfigParser import SafeConfigParser from dateutil import tz from distutils.version import LooseVersion from time import strptime from bloom.generators import BloomGenerator from bloom.generators import GeneratorError from bloom.generators import resolve_dependencies from bloom.generators import update_rosdep from bloom.generators.common import default_fallback_resolver from bloom.generators.common import invalidate_view_cache from bloom.generators.common import evaluate_package_conditions from bloom.generators.common import resolve_rosdep_key from bloom.git import inbranch from bloom.git import get_branches from bloom.git import get_commit_hash from bloom.git import get_current_branch from bloom.git import has_changes from bloom.git import show from bloom.git import tag_exists from bloom.logging import ansi from bloom.logging import debug from bloom.logging import enable_drop_first_log_prefix from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import warning from bloom.commands.git.patch.common import get_patch_config from bloom.commands.git.patch.common import set_patch_config from bloom.packages import get_package_data from bloom.util import code from bloom.util import execute_command from bloom.util import maybe_continue try: import rosdistro except ImportError as err: debug(traceback.format_exc()) error("rosdistro was not detected, please install it.", exit=True) try: import em except ImportError: debug(traceback.format_exc()) error("empy was not detected, please install it.", exit=True) # Drop the first log prefix for this command enable_drop_first_log_prefix(True) TEMPLATE_EXTENSION = '.em' def __place_template_folder(group, src, dst, gbp=False): template_files = pkg_resources.resource_listdir(group, src) # For each template, place for template_file in template_files: template_path = os.path.join(src, template_file) template_dst = os.path.join(dst, template_file) if pkg_resources.resource_isdir(group, template_path): debug("Recursing on folder '{0}'".format(template_path)) __place_template_folder(group, template_path, template_dst, gbp) else: try: debug("Placing template '{0}'".format(template_path)) template = pkg_resources.resource_string(group, template_path) template_abs_path = pkg_resources.resource_filename(group, template_path) except IOError as err: error("Failed to load template " "'{0}': {1}".format(template_file, str(err)), exit=True) if not os.path.exists(dst): os.makedirs(dst) if os.path.exists(template_dst): debug("Removing existing file '{0}'".format(template_dst)) os.remove(template_dst) with open(template_dst, 'w') as f: if not isinstance(template, str): template = template.decode('utf-8') f.write(template) shutil.copystat(template_abs_path, template_dst) def place_template_files(path, build_type, gbp=False): info(fmt("@!@{bf}==>@| Placing templates files in the 'rpm' folder.")) rpm_path = os.path.join(path, 'rpm') # Create/Clean the rpm folder if not os.path.exists(rpm_path): os.makedirs(rpm_path) # Place template files group = 'bloom.generators.rpm' templates = os.path.join('templates', build_type) __place_template_folder(group, templates, rpm_path, gbp) def summarize_dependency_mapping(data, deps, build_deps, resolved_deps): if len(deps) == 0 and len(build_deps) == 0: return info("Package '" + data['Package'] + "' has dependencies:") header = " " + ansi('boldoff') + ansi('ulon') + \ "rosdep key => " + data['OSName'] + ' ' + \ data['Distribution'] + " key" + ansi('reset') template = " " + ansi('cyanf') + "{0:<20} " + ansi('purplef') + \ "=> " + ansi('cyanf') + "{1}" + ansi('reset') if len(deps) != 0: info(ansi('purplef') + "Run Dependencies:" + ansi('reset')) info(header) for key in [d.name for d in deps]: info(template.format(key, resolved_deps[key])) if len(build_deps) != 0: info(ansi('purplef') + "Build and Build Tool Dependencies:" + ansi('reset')) info(header) for key in [d.name for d in build_deps]: info(template.format(key, resolved_deps[key])) def format_depends(depends, resolved_deps): versions = { 'version_lt': '<', 'version_lte': '<=', 'version_eq': '=', 'version_gte': '>=', 'version_gt': '>' } formatted = [] for d in depends: for resolved_dep in resolved_deps[d.name]: version_depends = [k for k in versions.keys() if getattr(d, k, None) is not None] if not version_depends: formatted.append(resolved_dep) else: for v in version_depends: formatted.append("{0} {1} {2}".format( resolved_dep, versions[v], getattr(d, v))) return formatted def missing_dep_resolver(key, peer_packages): if key in peer_packages: return [sanitize_package_name(key)] return default_fallback_resolver(key, peer_packages) def generate_substitutions_from_package( package, os_name, os_version, ros_distro, installation_prefix='/usr', rpm_inc=0, peer_packages=None, releaser_history=None, fallback_resolver=None, skip_keys=None ): peer_packages = peer_packages or [] skip_keys = skip_keys or set() data = {} # Name, Version, Description data['Name'] = package.name data['Version'] = package.version data['Description'] = rpmify_string(package.description) # License if not package.licenses or not package.licenses[0]: error("No license set for package '{0}', aborting.".format(package.name), exit=True) data['License'] = package.licenses[0] # Websites websites = [str(url) for url in package.urls if url.type == 'website'] data['Homepage'] = websites[0] if websites else '' if data['Homepage'] == '': warning("No homepage set") # RPM Increment Number data['RPMInc'] = rpm_inc # Package name data['Package'] = sanitize_package_name(package.name) # Installation prefix data['InstallationPrefix'] = installation_prefix # Resolve dependencies evaluate_package_conditions(package, ros_distro) depends = [ dep for dep in (package.run_depends + package.buildtool_export_depends) if dep.evaluated_condition is not False and dep.name not in skip_keys] build_depends = [ dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends) if dep.evaluated_condition is not False and dep.name not in skip_keys] replaces = [ dep for dep in package.replaces if dep.evaluated_condition is not False] conflicts = [ dep for dep in package.conflicts if dep.evaluated_condition is not False] unresolved_keys = depends + build_depends + replaces + conflicts # The installer key is not considered here, but it is checked when the keys are checked before this resolved_deps = resolve_dependencies(unresolved_keys, os_name, os_version, ros_distro, peer_packages + [d.name for d in (replaces + conflicts)], fallback_resolver) data['Depends'] = sorted( set(format_depends(depends, resolved_deps)) ) data['BuildDepends'] = sorted( set(format_depends(build_depends, resolved_deps)) ) data['Replaces'] = sorted( set(format_depends(replaces, resolved_deps)) ) data['Conflicts'] = sorted( set(format_depends(conflicts, resolved_deps)) ) data['Provides'] = [] data['Supplements'] = [] # Build-type specific substitutions. build_type = package.get_build_type() if build_type == 'catkin': pass elif build_type == 'cmake': pass elif build_type == 'ament_cmake': pass elif build_type == 'ament_python': pass else: error( "Build type '{}' is not supported by this version of bloom.". format(build_type), exit=True) # Set the OS and distribution data['OSName'] = os_name data['Distribution'] = os_version # Use the time stamp to set the date strings stamp = datetime.datetime.now(tz.tzlocal()) data['Date'] = stamp.strftime('%a %b %d %Y') # Maintainers maintainers = [] for m in package.maintainers: maintainers.append(str(m)) data['Maintainer'] = maintainers[0] data['Maintainers'] = ', '.join(maintainers) # Changelog if releaser_history: sorted_releaser_history = sorted(releaser_history, key=lambda k: LooseVersion(k), reverse=True) sorted_releaser_history = sorted(sorted_releaser_history, key=lambda k: strptime(releaser_history.get(k)[0], '%a %b %d %Y'), reverse=True) changelogs = [(v, releaser_history[v]) for v in sorted_releaser_history] else: # Ensure at least a minimal changelog changelogs = [] if package.version + '-' + str(rpm_inc) not in [x[0] for x in changelogs]: changelogs.insert(0, ( package.version + '-' + str(rpm_inc), ( data['Date'], package.maintainers[0].name, package.maintainers[0].email ) )) exported_tags = [e.tagname for e in package.exports] data['NoArch'] = 'metapackage' in exported_tags or 'architecture_independent' in exported_tags data['changelogs'] = changelogs # Summarize dependencies summarize_dependency_mapping(data, depends, build_depends, resolved_deps) def convertToUnicode(obj): if sys.version_info.major == 2: if isinstance(obj, str): return unicode(obj.decode('utf8')) elif isinstance(obj, unicode): return obj else: if isinstance(obj, bytes): return str(obj.decode('utf8')) elif isinstance(obj, str): return obj if isinstance(obj, list): for i, val in enumerate(obj): obj[i] = convertToUnicode(val) return obj elif isinstance(obj, type(None)): return None elif isinstance(obj, tuple): obj_tmp = list(obj) for i, val in enumerate(obj_tmp): obj_tmp[i] = convertToUnicode(obj_tmp[i]) return tuple(obj_tmp) elif isinstance(obj, int): return obj elif isinstance(obj, int): return obj raise RuntimeError('need to deal with type %s' % (str(type(obj)))) for item in data.items(): data[item[0]] = convertToUnicode(item[1]) return data def __process_template_folder(path, subs): items = os.listdir(path) processed_items = [] for item in list(items): item = os.path.abspath(os.path.join(path, item)) if os.path.basename(item) in ['.', '..', '.git', '.svn']: continue if os.path.isdir(item): sub_items = __process_template_folder(item, subs) processed_items.extend([os.path.join(item, s) for s in sub_items]) if not item.endswith(TEMPLATE_EXTENSION): continue with open(item, 'r') as f: template = f.read() # Remove extension template_path = item[:-len(TEMPLATE_EXTENSION)] # Expand template info("Expanding '{0}' -> '{1}'".format( os.path.relpath(item), os.path.relpath(template_path))) result = em.expand(template, **subs) # Write the result with io.open(template_path, 'w', encoding='utf-8') as f: if sys.version_info.major == 2: result = result.decode('utf-8') f.write(result) # Copy the permissions shutil.copymode(item, template_path) processed_items.append(item) return processed_items def process_template_files(path, subs): info(fmt("@!@{bf}==>@| In place processing templates in 'rpm' folder.")) rpm_dir = os.path.join(path, 'rpm') if not os.path.exists(rpm_dir): sys.exit("No rpm directory found at '{0}', cannot process templates." .format(rpm_dir)) return __process_template_folder(rpm_dir, subs) def match_branches_with_prefix(prefix, get_branches, prune=False): debug("match_branches_with_prefix(" + str(prefix) + ", " + str(get_branches()) + ")") branches = [] # Match branches existing_branches = get_branches() for branch in existing_branches: if branch.startswith('remotes/origin/'): branch = branch.split('/', 2)[-1] if branch.startswith(prefix): branches.append(branch) branches = list(set(branches)) if prune: # Prune listed branches by packages in latest upstream with inbranch('upstream'): pkg_names, version, pkgs_dict = get_package_data('upstream') for branch in branches: if branch.split(prefix)[-1].strip('/') not in pkg_names: branches.remove(branch) return branches def get_package_from_branch(branch): with inbranch(branch): try: package_data = get_package_data(branch) except SystemExit: return None if type(package_data) not in [list, tuple]: # It is a ret code RpmGenerator.exit(package_data) names, version, packages = package_data if type(names) is list and len(names) > 1: RpmGenerator.exit( "RPM generator does not support generating " "from branches with multiple packages in them, use " "the release generator first to split packages into " "individual branches.") if type(packages) is dict: return list(packages.values())[0] def rpmify_string(value): markup_remover = re.compile(r'<.*?>') value = markup_remover.sub('', value) value = re.sub('\s+', ' ', value) value = '\n'.join([v.strip() for v in textwrap.TextWrapper(width=80, break_long_words=False, replace_whitespace=False).wrap(value)]) return value def sanitize_package_name(name): return name.replace('_', '-') class RpmGenerator(BloomGenerator): title = 'rpm' description = "Generates RPMs from the catkin meta data" has_run_rosdep = os.environ.get('BLOOM_SKIP_ROSDEP_UPDATE', '0').lower() not in ['0', 'f', 'false', 'n', 'no'] default_install_prefix = '/usr' rosdistro = os.environ.get('ROS_DISTRO', 'indigo') def prepare_arguments(self, parser): # Add command line arguments for this generator add = parser.add_argument add('-i', '--rpm-inc', help="RPM increment number", default='0') add('-p', '--prefix', required=True, help="branch prefix to match, and from which create RPMs" " hint: if you want to match 'release/foo' use 'release'") add('-a', '--match-all', default=False, action="store_true", help="match all branches with the given prefix, " "even if not in current upstream") add('--distros', nargs='+', required=False, default=[], help='A list of RPM (fedora) distros to generate for') add('--install-prefix', default=None, help="overrides the default installation prefix (/usr)") add('--os-name', default='fedora', help="overrides os_name, set to 'fedora' by default") add('--skip-keys', nargs='+', required=False, default=[], help="dependency keys which should be skipped and" " discluded from the RPM dependencies") def handle_arguments(self, args): self.interactive = args.interactive self.rpm_inc = args.rpm_inc self.os_name = args.os_name self.distros = args.distros self.skip_keys = args.skip_keys or set() if self.distros in [None, []]: index = rosdistro.get_index(rosdistro.get_index_url()) distribution_file = rosdistro.get_distribution_file(index, self.rosdistro) if self.os_name not in distribution_file.release_platforms: warning("No platforms defined for os '{0}' in release file for the '{1}' distro." "\nNot performing RPM generation." .format(self.os_name, self.rosdistro)) sys.exit(0) self.distros = distribution_file.release_platforms[self.os_name] self.install_prefix = args.install_prefix if args.install_prefix is None: self.install_prefix = self.default_install_prefix self.prefix = args.prefix self.branches = match_branches_with_prefix(self.prefix, get_branches, prune=not args.match_all) if len(self.branches) == 0: error( "No packages found, check your --prefix or --src arguments.", exit=True ) self.packages = {} self.tag_names = {} self.names = [] self.branch_args = [] self.rpm_branches = [] for branch in self.branches: package = get_package_from_branch(branch) if package is None: # This is an ignored package continue self.packages[package.name] = package self.names.append(package.name) args = self.generate_branching_arguments(package, branch) # First branch is rpm/[/] self.rpm_branches.append(args[0][0]) self.branch_args.extend(args) def summarize(self): info("Generating source RPMs for the packages: " + str(self.names)) info("RPM Incremental Version: " + str(self.rpm_inc)) info("RPM OS: " + str(self.os_name)) info("RPM Distributions: " + str(self.distros)) def get_branching_arguments(self): return self.branch_args def update_rosdep(self): update_rosdep() self.has_run_rosdep = True def _check_all_keys_are_valid(self, peer_packages, rosdistro): keys_to_resolve = set() key_to_packages_which_depends_on = collections.defaultdict(list) keys_to_ignore = set() for package in self.packages.values(): evaluate_package_conditions(package, rosdistro) depends = [ dep for dep in (package.run_depends + package.buildtool_export_depends) if dep.evaluated_condition is not False] build_depends = [ dep for dep in (package.build_depends + package.buildtool_depends + package.test_depends) if dep.evaluated_condition is not False] unresolved_keys = [ dep for dep in (depends + build_depends + package.replaces + package.conflicts) if dep.evaluated_condition is not False] keys_to_ignore = { dep for dep in keys_to_ignore.union(package.replaces + package.conflicts) if dep.evaluated_condition is not False} keys = [d.name for d in unresolved_keys] keys_to_resolve.update(keys) for key in keys: key_to_packages_which_depends_on[key].append(package.name) for skip_key in self.skip_keys: try: keys_to_resolve.remove(skip_key) except KeyError: warning("Key '{0}' specified by --skip-keys was not found".format(skip_key)) else: warning("Skipping dependency key '{0}' per --skip-keys".format(skip_key)) os_name = self.os_name rosdistro = self.rosdistro all_keys_valid = True for key in sorted(keys_to_resolve): for os_version in self.distros: try: extended_peer_packages = peer_packages + [d.name for d in keys_to_ignore] rule, installer_key, default_installer_key = \ resolve_rosdep_key(key, os_name, os_version, rosdistro, extended_peer_packages, retry=False) if rule is None: continue if installer_key != default_installer_key: error("Key '{0}' resolved to '{1}' with installer '{2}', " "which does not match the default installer '{3}'." .format(key, rule, installer_key, default_installer_key)) BloomGenerator.exit( "The RPM generator does not support dependencies " "which are installed with the '{0}' installer." .format(installer_key), returncode=code.GENERATOR_INVALID_INSTALLER_KEY) except (GeneratorError, RuntimeError) as e: print(fmt("Failed to resolve @{cf}@!{key}@| on @{bf}{os_name}@|:@{cf}@!{os_version}@| with: {e}") .format(**locals())) print(fmt("@{cf}@!{0}@| is depended on by these packages: ").format(key) + str(list(set(key_to_packages_which_depends_on[key])))) print(fmt("@{kf}@!<== @{rf}@!Failed@|")) all_keys_valid = False return all_keys_valid def pre_modify(self): info("\nPre-verifying RPM dependency keys...") # Run rosdep update is needed if not self.has_run_rosdep: self.update_rosdep() peer_packages = [p.name for p in self.packages.values()] while not self._check_all_keys_are_valid(peer_packages, self.rosdistro): error("Some of the dependencies for packages in this repository could not be resolved by rosdep.") if not self.interactive: sys.exit(code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO) error("You can try to address the issues which appear above and try again if you wish, " "or continue without releasing into RPM-based distributions (e.g. Fedora 24).") try: if not maybe_continue(msg="Would you like to try again?"): error("User aborted after rosdep keys were not resolved.") sys.exit(code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO) except (KeyboardInterrupt, EOFError): error("\nUser quit.", exit=True) update_rosdep() invalidate_view_cache() info("All keys are " + ansi('greenf') + "OK" + ansi('reset') + "\n") for package in self.packages.values(): if not package.licenses or not package.licenses[0]: error("No license set for package '{0}', aborting.".format(package.name), exit=True) def pre_branch(self, destination, source): if destination in self.rpm_branches: return # Run rosdep update is needed if not self.has_run_rosdep: self.update_rosdep() # Determine the current package being generated name = destination.split('/')[-1] distro = destination.split('/')[-2] # Retrieve the package package = self.packages[name] # Report on this package self.summarize_package(package, distro) def pre_rebase(self, destination): # Get the stored configs is any patches_branch = 'patches/' + destination config = self.load_original_config(patches_branch) if config is not None: curr_config = get_patch_config(patches_branch) if curr_config['parent'] == config['parent']: set_patch_config(patches_branch, config) def post_rebase(self, destination): name = destination.split('/')[-1] # Retrieve the package package = self.packages[name] # Handle differently if this is an rpm vs distro branch if destination in self.rpm_branches: info("Placing RPM template files into '{0}' branch." .format(destination)) # Then this is an rpm branch # Place the raw template files self.place_template_files(package.get_build_type()) else: # This is a distro specific rpm branch # Determine the current package being generated distro = destination.split('/')[-2] # Create RPMs for each distro with inbranch(destination): data = self.generate_rpm(package, distro) # Create the tag name for later self.tag_names[destination] = self.generate_tag_name(data) # Update the patch configs patches_branch = 'patches/' + destination config = get_patch_config(patches_branch) # Store it self.store_original_config(config, patches_branch) # Modify the base so import/export patch works current_branch = get_current_branch() if current_branch is None: error("Could not determine current branch.", exit=True) config['base'] = get_commit_hash(current_branch) # Set it set_patch_config(patches_branch, config) def post_patch(self, destination, color='bluef'): if destination in self.rpm_branches: return # Tag after patches have been applied with inbranch(destination): # Tag tag_name = self.tag_names[destination] if tag_exists(tag_name): if self.interactive: warning("Tag exists: " + tag_name) warning("Do you wish to overwrite it?") if not maybe_continue('y'): error("Answered no to continue, aborting.", exit=True) else: warning("Overwriting tag: " + tag_name) else: info("Creating tag: " + tag_name) execute_command('git tag -f ' + tag_name) # Report of success name = destination.split('/')[-1] package = self.packages[name] distro = destination.split('/')[-2] info(ansi(color) + "####" + ansi('reset'), use_prefix=False) info( ansi(color) + "#### " + ansi('greenf') + "Successfully" + ansi(color) + " generated '" + ansi('boldon') + self.os_name + ' ' + distro + ansi('boldoff') + "' RPM for package" " '" + ansi('boldon') + package.name + ansi('boldoff') + "'" + " at version '" + ansi('boldon') + package.version + "-" + str(self.rpm_inc) + ansi('boldoff') + "'" + ansi('reset'), use_prefix=False ) info(ansi(color) + "####\n" + ansi('reset'), use_prefix=False) def store_original_config(self, config, patches_branch): with inbranch(patches_branch): with open('rpm.store', 'w+') as f: f.write(json.dumps(config)) execute_command('git add rpm.store') if has_changes(): execute_command('git commit -m "Store original patch config"') def load_original_config(self, patches_branch): config_store = show(patches_branch, 'rpm.store') if config_store is None: return config_store return json.loads(config_store) def place_template_files(self, build_type, rpm_dir='rpm'): # Create/Clean the rpm folder if os.path.exists(rpm_dir): if self.interactive: warning("rpm directory exists: " + rpm_dir) warning("Do you wish to overwrite it?") if not maybe_continue('y'): error("Answered no to continue, aborting.", exit=True) else: warning("Overwriting rpm directory: " + rpm_dir) execute_command('git rm -rf ' + rpm_dir) execute_command('git commit -m "Clearing previous rpm folder"') if os.path.exists(rpm_dir): shutil.rmtree(rpm_dir) # Use generic place template files command place_template_files('.', build_type, gbp=True) # Commit results execute_command('git add ' + rpm_dir) execute_command('git commit -m "Placing rpm template files"') def get_releaser_history(self): # Assumes that this is called in the target branch patches_branch = 'patches/' + get_current_branch() raw = show(patches_branch, 'releaser_history.json') return None if raw is None else json.loads(raw) def set_releaser_history(self, history): # Assumes that this is called in the target branch patches_branch = 'patches/' + get_current_branch() debug("Writing release history to '{0}' branch".format(patches_branch)) with inbranch(patches_branch): with open('releaser_history.json', 'w') as f: f.write(json.dumps(history)) execute_command('git add releaser_history.json') if has_changes(): execute_command('git commit -m "Store releaser history"') def get_subs(self, package, rpm_distro, releaser_history=None): return generate_substitutions_from_package( package, self.os_name, rpm_distro, self.rosdistro, self.install_prefix, self.rpm_inc, [p.name for p in self.packages.values()], releaser_history=releaser_history, fallback_resolver=missing_dep_resolver, skip_keys=self.skip_keys ) def generate_rpm(self, package, rpm_distro, rpm_dir='rpm'): info("Generating RPM for {0} {1}...".format(self.os_name, rpm_distro)) # Try to retrieve the releaser_history releaser_history = self.get_releaser_history() # Generate substitution values subs = self.get_subs(package, rpm_distro, releaser_history) # Use subs to create and store releaser history self.set_releaser_history(dict(subs['changelogs'])) # Template files template_files = process_template_files('.', subs) # Remove any residual template files execute_command('git rm -rf ' + ' '.join("'{}'".format(t) for t in template_files)) # Add marker file to tell mock to archive the sources open('.write_tar', 'a').close() # Add marker file changes to the rpm folder execute_command('git add .write_tar ' + rpm_dir) # Commit changes execute_command('git commit -m "Generated RPM files for ' + rpm_distro + '"') # Rename the template spec file execute_command('git mv ' + rpm_dir + '/template.spec ' + rpm_dir + '/' + subs['Package'] + '.spec') # Commit changes execute_command('git commit -m "Renamed RPM spec file for ' + rpm_distro + '"') # Return the subs for other use return subs def generate_tag_name(self, data): tag_name = '{Package}-{Version}-{RPMInc}_{Distribution}' tag_name = 'rpm/' + tag_name.format(**data) return tag_name def generate_branching_arguments(self, package, branch): n = package.name # rpm branch rpm_branch = 'rpm/' + n # Branch first to the rpm branch args = [[rpm_branch, branch, False]] # Then for each RPM distro, branch from the base rpm branch args.extend([ ['rpm/' + d + '/' + n, rpm_branch, False] for d in self.distros ]) return args def summarize_package(self, package, distro, color='bluef'): info(ansi(color) + "\n####" + ansi('reset'), use_prefix=False) info( ansi(color) + "#### Generating '" + ansi('boldon') + self.os_name + ' ' + distro + ansi('boldoff') + "' RPM for package" " '" + ansi('boldon') + package.name + ansi('boldoff') + "'" + " at version '" + ansi('boldon') + package.version + "-" + str(self.rpm_inc) + ansi('boldoff') + "'" + ansi('reset'), use_prefix=False ) info(ansi(color) + "####" + ansi('reset'), use_prefix=False) bloom-0.10.7/bloom/generators/rpm/templates/000077500000000000000000000000001403641720100207625ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/templates/ament_cmake/000077500000000000000000000000001403641720100232265ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/templates/ament_cmake/template.spec.em000066400000000000000000000056461403641720100263300ustar00rootroot00000000000000%bcond_without tests %bcond_without weak_deps %global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g') %global __provides_exclude_from ^@(InstallationPrefix)/.*$ %global __requires_exclude_from ^@(InstallationPrefix)/.*$ Name: @(Package) Version: @(Version) Release: @(RPMInc)%{?dist}%{?release_suffix} Summary: ROS @(Name) package License: @(License) @[if Homepage and Homepage != '']URL: @(Homepage)@\n@[end if]@ Source0: %{name}-%{version}.tar.gz @[if NoArch]@\nBuildArch: noarch@\n@[end if]@ @[for p in Depends]Requires: @p@\n@[end for]@ @[for p in BuildDepends]BuildRequires: @p@\n@[end for]@ @[for p in Conflicts]Conflicts: @p@\n@[end for]@ @[for p in Replaces]Obsoletes: @p@\n@[end for]@ @[for p in Provides]Provides: @p@\n@[end for]@ @[if Supplements]@\n%if 0%{?with_weak_deps} @[for p in Supplements]Supplements: @p@\n@[end for]@ %endif@\n@[end if]@ %description @(Description) %prep %autosetup -p1 %build # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi mkdir -p obj-%{_target_platform} && cd obj-%{_target_platform} %cmake3 \ -UINCLUDE_INSTALL_DIR \ -ULIB_INSTALL_DIR \ -USYSCONF_INSTALL_DIR \ -USHARE_INSTALL_PREFIX \ -ULIB_SUFFIX \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DAMENT_PREFIX_PATH="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" \ -DSETUPTOOLS_DEB_LAYOUT=OFF \ .. %make_build %install # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %make_install -C obj-%{_target_platform} %if 0%{?with_tests} %check # Look for a Makefile target with a name indicating that it runs tests TEST_TARGET=$(%__make -qp -C obj-%{_target_platform} | sed "s/^\(test\|check\):.*/\\1/;t f;d;:f;q0") if [ -n "$TEST_TARGET" ]; then # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi CTEST_OUTPUT_ON_FAILURE=1 \ %make_build -C obj-%{_target_platform} $TEST_TARGET || echo "RPM TESTS FAILED" else echo "RPM TESTS SKIPPED"; fi %endif %files @(InstallationPrefix) %changelog@ @[for change_version, (change_date, main_name, main_email) in changelogs] * @(change_date) @(main_name) <@(main_email)> - @(change_version) - Autogenerated by Bloom @[end for] bloom-0.10.7/bloom/generators/rpm/templates/ament_python/000077500000000000000000000000001403641720100234675ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/templates/ament_python/template.spec.em000066400000000000000000000050221403641720100265550ustar00rootroot00000000000000%bcond_without tests %bcond_without weak_deps %global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g') %global __provides_exclude_from ^@(InstallationPrefix)/.*$ %global __requires_exclude_from ^@(InstallationPrefix)/.*$ Name: @(Package) Version: @(Version) Release: @(RPMInc)%{?dist}%{?release_suffix} Summary: ROS @(Name) package License: @(License) @[if Homepage and Homepage != '']URL: @(Homepage)@\n@[end if]@ Source0: %{name}-%{version}.tar.gz @[if NoArch]@\nBuildArch: noarch@\n@[end if]@ @[for p in Depends]Requires: @p@\n@[end for]@ @[for p in sorted(BuildDepends + ['python%{python3_pkgversion}-devel'])]BuildRequires: @p@\n@[end for]@ @[for p in Conflicts]Conflicts: @p@\n@[end for]@ @[for p in Replaces]Obsoletes: @p@\n@[end for]@ @[for p in Provides]Provides: @p@\n@[end for]@ @[if Supplements]@\n%if 0%{?with_weak_deps} @[for p in Supplements]Supplements: @p@\n@[end for]@ %endif@\n@[end if]@ %description @(Description) %prep %autosetup -p1 %build # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %py3_build %install # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %py3_install -- --prefix "@(InstallationPrefix)" %if 0%{?with_tests} %check # Look for a directory with a name indicating that it contains tests TEST_TARGET=$(ls -d * | grep -m1 "\(test\|tests\)" ||:) if [ -n "$TEST_TARGET" ] && %__python3 -m pytest --version; then # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %__python3 -m pytest $TEST_TARGET || echo "RPM TESTS FAILED" else echo "RPM TESTS SKIPPED"; fi %endif %files @(InstallationPrefix) %changelog@ @[for change_version, (change_date, main_name, main_email) in changelogs] * @(change_date) @(main_name) <@(main_email)> - @(change_version) - Autogenerated by Bloom @[end for] bloom-0.10.7/bloom/generators/rpm/templates/catkin/000077500000000000000000000000001403641720100222335ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/templates/catkin/template.spec.em000066400000000000000000000044511403641720100253260ustar00rootroot00000000000000%bcond_without weak_deps %global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g') %global __provides_exclude_from ^@(InstallationPrefix)/.*$ %global __requires_exclude_from ^@(InstallationPrefix)/.*$ Name: @(Package) Version: @(Version) Release: @(RPMInc)%{?dist}%{?release_suffix} Summary: ROS @(Name) package License: @(License) @[if Homepage and Homepage != '']URL: @(Homepage)@\n@[end if]@ Source0: %{name}-%{version}.tar.gz @[if NoArch]@\nBuildArch: noarch@\n@[end if]@ @[for p in Depends]Requires: @p@\n@[end for]@ @[for p in BuildDepends]BuildRequires: @p@\n@[end for]@ @[for p in Conflicts]Conflicts: @p@\n@[end for]@ @[for p in Replaces]Obsoletes: @p@\n@[end for]@ @[for p in Provides]Provides: @p@\n@[end for]@ @[if Supplements]@\n%if 0%{?with_weak_deps} @[for p in Supplements]Supplements: @p@\n@[end for]@ %endif@\n@[end if]@ %description @(Description) %prep %autosetup -p1 %build # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi mkdir -p obj-%{_target_platform} && cd obj-%{_target_platform} %cmake3 \ -UINCLUDE_INSTALL_DIR \ -ULIB_INSTALL_DIR \ -USYSCONF_INSTALL_DIR \ -USHARE_INSTALL_PREFIX \ -ULIB_SUFFIX \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" \ -DSETUPTOOLS_DEB_LAYOUT=OFF \ -DCATKIN_BUILD_BINARY_PACKAGE="1" \ .. %make_build %install # In case we're installing to a non-standard location, look for a setup.sh # in the install tree that was dropped by catkin, and source it. It will # set things like CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %make_install -C obj-%{_target_platform} %files @(InstallationPrefix) %changelog@ @[for change_version, (change_date, main_name, main_email) in changelogs] * @(change_date) @(main_name) <@(main_email)> - @(change_version) - Autogenerated by Bloom @[end for] bloom-0.10.7/bloom/generators/rpm/templates/cmake/000077500000000000000000000000001403641720100220425ustar00rootroot00000000000000bloom-0.10.7/bloom/generators/rpm/templates/cmake/template.spec.em000066400000000000000000000055641403641720100251430ustar00rootroot00000000000000%bcond_without tests %bcond_without weak_deps %global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g') %global __provides_exclude_from ^@(InstallationPrefix)/.*$ %global __requires_exclude_from ^@(InstallationPrefix)/.*$ Name: @(Package) Version: @(Version) Release: @(RPMInc)%{?dist}%{?release_suffix} Summary: ROS @(Name) package License: @(License) @[if Homepage and Homepage != '']URL: @(Homepage)@\n@[end if]@ Source0: %{name}-%{version}.tar.gz @[if NoArch]@\nBuildArch: noarch@\n@[end if]@ @[for p in Depends]Requires: @p@\n@[end for]@ @[for p in BuildDepends]BuildRequires: @p@\n@[end for]@ @[for p in Conflicts]Conflicts: @p@\n@[end for]@ @[for p in Replaces]Obsoletes: @p@\n@[end for]@ @[for p in Provides]Provides: @p@\n@[end for]@ @[if Supplements]@\n%if 0%{?with_weak_deps} @[for p in Supplements]Supplements: @p@\n@[end for]@ %endif@\n@[end if]@ %description @(Description) %prep %autosetup -p1 %build # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi mkdir -p obj-%{_target_platform} && cd obj-%{_target_platform} %cmake3 \ -UINCLUDE_INSTALL_DIR \ -ULIB_INSTALL_DIR \ -USYSCONF_INSTALL_DIR \ -USHARE_INSTALL_PREFIX \ -ULIB_SUFFIX \ -DCMAKE_INSTALL_PREFIX="@(InstallationPrefix)" \ -DCMAKE_PREFIX_PATH="@(InstallationPrefix)" \ -DSETUPTOOLS_DEB_LAYOUT=OFF \ .. %make_build %install # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi %make_install -C obj-%{_target_platform} %if 0%{?with_tests} %check # Look for a Makefile target with a name indicating that it runs tests TEST_TARGET=$(%__make -qp -C obj-%{_target_platform} | sed "s/^\(test\|check\):.*/\\1/;t f;d;:f;q0") if [ -n "$TEST_TARGET" ]; then # In case we're installing to a non-standard location, look for a setup.sh # in the install tree and source it. It will set things like # CMAKE_PREFIX_PATH, PKG_CONFIG_PATH, and PYTHONPATH. if [ -f "@(InstallationPrefix)/setup.sh" ]; then . "@(InstallationPrefix)/setup.sh"; fi CTEST_OUTPUT_ON_FAILURE=1 \ %make_build -C obj-%{_target_platform} $TEST_TARGET || echo "RPM TESTS FAILED" else echo "RPM TESTS SKIPPED"; fi %endif %files @(InstallationPrefix) %changelog@ @[for change_version, (change_date, main_name, main_email) in changelogs] * @(change_date) @(main_name) <@(main_email)> - @(change_version) - Autogenerated by Bloom @[end for] bloom-0.10.7/bloom/git.py000077500000000000000000000632741403641720100151710ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Advanced utilities for manipulating git repositories""" from __future__ import print_function import os import functools import shutil import subprocess import tempfile from subprocess import PIPE from subprocess import CalledProcessError from pkg_resources import parse_version from bloom.logging import debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import warning from bloom.util import change_directory from bloom.util import check_output from bloom.util import execute_command from bloom.util import get_git_clone_state from bloom.util import get_git_clone_state_quiet from bloom.util import pdb_hook import bloom.util class GitClone(object): def __init__(self, directory=None, track_all=True): self.disabled = get_git_clone_state() self.disabled_quiet = get_git_clone_state_quiet() if self.disabled: if not self.disabled_quiet: warning('Skipping transactional safety mechanism, be careful...') return self.tmp_dir = None self.directory = directory if directory is not None else os.getcwd() if get_root(directory) is None: raise RuntimeError("Provided directory, '" + str(directory) + "', is not a git repository") self.track_all = track_all if self.track_all: track_branches(directory=directory) self.current_branches = get_branches() self.tmp_dir = tempfile.mkdtemp() self.clone_dir = os.path.join(self.tmp_dir, 'clone') self.repo_url = 'file://' + os.path.abspath(self.directory) info(fmt("@!@{gf}+++@| Cloning working copy for safety")) execute_command('git clone ' + self.repo_url + ' ' + self.clone_dir) def __del__(self): if self.disabled: return self.clean_up() def __enter__(self): if self.disabled: return current_branch = get_current_branch() if current_branch is None: warning("Could not determine current branch, changing to the bloom branch") execute_command('git checkout bloom') self.orig_cwd = os.getcwd() os.chdir(self.clone_dir) if self.track_all: track_branches(directory=self.clone_dir) return os.getcwd() def __exit__(self, exc_type, exc_value, traceback): if self.disabled: return os.chdir(self.orig_cwd) def clean_up(self): if self.disabled: return if self.tmp_dir is not None and os.path.exists(self.tmp_dir): shutil.rmtree(self.tmp_dir) self.tmp_dir = None def commit(self): if self.disabled: return info(fmt("@{bf}<==@| Command successful, committing changes to working copy")) current_branch = get_current_branch() if current_branch is None: error("Could not determine current branch.", exit=True) with inbranch(get_commit_hash(get_current_branch())): with change_directory(self.clone_dir): new_branches = get_branches() for branch in self.current_branches: if branch in new_branches: new_branches.remove(branch) for branch in get_branches(local_only=True): if branch not in new_branches: with inbranch(branch): cmd = 'git pull --rebase origin ' + branch execute_command(cmd) execute_command('git push --all', silent=False) try: execute_command('git push --tags', silent=False) except subprocess.CalledProcessError: warning("Force pushing tags from clone to working repository, " "you will have to force push back to origin...") execute_command('git push --force --tags', silent=False) self.clean_up() def ls_tree(reference, path=None, directory=None): """ Returns a dictionary of files and folders for a given reference and path. Implemented using ``git ls-tree``. If an invalid reference and/or path None is returned. :param reference: git reference to pull from (branch, tag, or commit) :param path: tree to list :param directory: directory in which to run this command :returns: dict if a directory (or a reference) or None if it does not exist :raises: subprocess.CalledProcessError if any git calls fail :raises: RuntimeError if the output from git is not what we expected """ # Try to track the reference as a branch track_branches(reference, directory=directory) cmd = 'git ls-tree ' + reference if path is not None and path != '': cmd += ':' + path retcode, out, err = execute_command(cmd, autofail=False, silent_error=True, cwd=directory, return_io=True) if retcode != 0: return None items = {} for line in out.splitlines(): tokens = line.split() if len(tokens) != 4: return None if tokens[1] not in ['blob', 'tree']: raise RuntimeError("item not a blob or tree") if tokens[3] in items: raise RuntimeError("duplicate name in ls tree") items[tokens[3]] = 'file' if tokens[1] == 'blob' else 'directory' return items def show(reference, path, directory=None): """ Interface to the git show command. If path is a file that exists, a string will be returned which is the contents of that file. If the path is a directory that exists, then a dictionary is returned where the keys are items in the folder and the value is either the string 'file' or 'directory'. If the path does not exist then this returns None. :param reference: git reference to pull from (branch, tag, or commit) :param path: path to show or list :param directory: directory in which to run this command :returns: string if a file, dict if a directory, None if it does not exist :raises: subprocess.CalledProcessError if any git calls fail """ # Check to see if this is a directory dirs = ls_tree(reference, path, directory) if dirs is not None: return dirs # Otherwise a file or does not exist, check for the file cmd = 'git show {0}:{1}'.format(reference, path) # Check to see if it is a directory retcode, out, err = execute_command(cmd, autofail=False, silent_error=True, cwd=directory, return_io=True) if retcode != 0: # Does not exist return None # It is a file that exists, return the output return out def ensure_clean_working_env(force=False, git_status=True, directory=None): """ Checks the environment to ensure it is clean, raises SystemExit otherwise. Clean is defined as: - In a git repository - In a valid branch (force overrides) - Does not have local changes (force overrides) - Does not have untracked files (force overrides) :param force: If True, overrides a few of the fail conditions :param directory: directory in which to run this command :raises: subprocess.CalledProcessError if any git calls fail :raises: SystemExit if any git calls fail """ def ecwe_fail(code, show_git_status): if not bloom.util._quiet and show_git_status: info('\n++ git status:\n', use_prefix=False) os.system('git status') error(code, exit=True) # Is it a git repo if get_root(directory) is None: error("Not in a valid git repository", exit=True) # Are we on a branch? current_branch = get_current_branch(directory) if current_branch is None: msg = "Could not determine current branch" if not force: return ecwe_fail(msg, git_status) else: warning(msg) # Are there local changes? if has_changes(directory): msg = "Current git working branch has local changes" if not force: return ecwe_fail(msg, git_status) else: warning(msg) # Are there untracked files or directories? if has_untracked_files(directory): msg = "Current git working branch has untracked files/directories" if not force: return ecwe_fail(msg, git_status) else: warning(msg) def checkout(reference, raise_exc=False, directory=None, show_git_status=True): """ Returns True if the checkout to a the reference was successful, else False :param reference: branch, tag, or commit hash to checkout to :param directory: directory in which to run this command :returns: True if the checkout was successful, else False """ def checkout_summarize(fail_msg, branch, directory): branch = '(no branch)' if branch is None else branch directory = os.getcwd() if directory is None else directory error("Failed to checkout to '{0}'".format(str(reference)) + " because the working directory {0}".format(str(fail_msg))) debug(" Working directory: '{0}'".format(str(directory))) debug(" Working branch: '{0}'".format(str(branch))) debug(" Has local changes: '{0}'".format(str(changes))) debug(" Has untrakced files: '{0}'".format(str(untracked))) pdb_hook() if not bloom.util._quiet and show_git_status: info('\n++ git status:\n', use_prefix=False) os.system('git status') return False debug("Checking out to " + str(reference)) if reference == get_current_branch(directory): debug("Requested checkout reference is the same as the current branch") return True fail_msg = '' git_root = get_root(directory) if git_root is not None: changes = has_changes(directory) untracked = has_untracked_files(directory) branch = get_current_branch(directory) or 'could not determine branch' else: fail_msg = "is not a git repository" if fail_msg == '' and changes: fail_msg = "has local changes" if fail_msg == '' and untracked: fail_msg = "has untracked files" try: if not changes and not untracked: execute_command('git checkout "{0}"'.format(str(reference)), cwd=directory) except CalledProcessError as err: fail_msg = "CalledProcessError: " + str(err) if raise_exc: checkout_summarize(fail_msg, branch, directory) raise if fail_msg != '': return checkout_summarize(fail_msg, branch, directory) else: return True class ContextDecorator(object): def __call__(self, f): @functools.wraps(f) def decorated(*args, **kwds): with self: return f(*args, **kwds) return decorated class inbranch(ContextDecorator): """ Safely switches to a given branch on entry and switches back on exit. Combination decorator/context manager, therefore it can be used like: @inbranch('some_git_branch') def foo(): pass Or in conjunction with the 'with' statement: with inbranch('some_git_branch'): foo() :param branch_name: name of the branch to switch to :param directory: directory in which to run the branch change :raises: subprocess.CalledProcessError if either 'git checkout' call fails """ def __init__(self, branch, directory=None): self.branch = branch self.directory = directory def __enter__(self): self.current_branch = get_current_branch(self.directory) checkout(self.branch, raise_exc=True, directory=self.directory) def __exit__(self, exc_type, exc_value, traceback): if self.current_branch is not None: checkout(self.current_branch, raise_exc=True, directory=self.directory) else: warning("Could not determine branch to return to.") def get_commit_hash(reference, directory=None): """ Returns the SHA-1 commit hash for the given reference. :param reference: any git reference (branch or tag) to resolve to SHA-1 :param directory: directory in which to preform this action :returns: SHA-1 commit hash for the given reference :raises: subprocess.CalledProcessError if any git calls fail """ # Track remote branch if branch_exists(reference, local_only=False, directory=directory): if not branch_exists(reference, local_only=True, directory=directory): track_branches(reference, directory) cmd = 'git show-branch --sha1-name ' + reference out = check_output(cmd, shell=True, cwd=directory) return out.split('[')[1].split(']')[0] def has_untracked_files(directory=None): """ Returns True is the working branch has untracked files, False otherwise. :param directory: directory in which to preform this action :returns: True if there are untracked files (or dirs), otherwise False :raises: subprocess.CalledProcessError if any git calls fail """ out = check_output('git status', shell=True, cwd=directory) if '# Untracked files:' in out: return True return False def has_changes(directory=None): """ Returns True if the working branch has local changes, False otherwise. :param directory: directory in which to preform this action :returns: True if there are local changes, otherwise False :raises: subprocess.CalledProcessError if any git calls fail """ out = check_output('git status', shell=True, cwd=directory) if 'nothing to commit (working directory clean)' in out: return False if 'nothing to commit, working directory clean' in out: return False if 'nothing to commit, working tree clean' in out: return False if 'nothing added to commit' in out: return False return True def tag_exists(tag, directory=None): """ Returns True if the given tag exists, False otherwise :param tag: tag to check for :param directory: directory in which to preform this action :returns: True if the given tag exists, False otherwise :raises: subprocess.CalledProcessError if any git calls fail """ return tag in get_tags(directory) def create_tag(tag, directory=None): """ Creates a given tag :param tag: tag to create :param directory: directory in which to preform this action :raises: subprocess.CalledProcessError if any git calls fail """ execute_command('git tag {0}'.format(tag), shell=True, cwd=directory) def delete_tag(tag, directory=None): """ Deletes a given local tag. :param tag: local tag to delete :param directory: directory in which to preform this action :raises: subprocess.CalledProcessError if any git calls fail """ execute_command('git tag -d {0}'.format(tag), shell=True, cwd=directory) def delete_remote_tag(tag, remote='origin', directory=None): """ Deletes a given remote tag. :param tag: remote tag to delete :param remote: git remote to delete tag from (defaults to 'origin') :param directory: directory in which to preform this action :raises: subprocess.CalledProcessError if any git calls fail """ execute_command('git push {0} :{1}'.format(remote, tag), shell=True, cwd=directory) def get_tags(directory=None): """ Returns a list of tags in the git repository. :param directory: directory in which to preform this action :returns: list of tags :raises: subprocess.CalledProcessError if any git calls fail """ out = check_output('git tag -l', shell=True, cwd=directory) return [l.strip() for l in out.splitlines()] def branch_exists(branch_name, local_only=False, directory=None): """ Returns true if a given branch exists locally or remotelly :param branch_name: name of the branch to look for :param local_only: if True, only look at the local branches :param directory: directory in which to run this command :returns: True if the branch is in the list of branches from git :raises: subprocess.CalledProcessError if any git calls fail """ for branch in get_branches(local_only, directory): if branch.startswith('remotes/'): branch = branch.split('/') if len(branch) > 2: branch = '/'.join(branch[2:]) if branch_name == branch: return True else: if branch_name == branch: return True return False def get_branches(local_only=False, directory=None): """ Returns a list of branches in the git repository. :param local_only: if True, do not return remote branches, False by default :param directory: directory in which to preform this action :returns: list of branches :raises: subprocess.CalledProcessError if any git calls fail """ cmd = 'git branch --no-color' if not local_only: cmd += ' -a' out = check_output(cmd, shell=True, cwd=directory) branches = [] for line in out.splitlines(): if line.count('HEAD -> ') > 0: continue if line.count('(no branch)') > 0: continue line = line.strip('*').strip() branches.append(line) return branches def create_branch(branch, orphaned=False, changeto=False, directory=None): """ Creates a new branch in the current, or given, git repository. If the specified branch already exists git will fail and a subprocess.CalledProcessError will be raised. :param branch: name of the new branch :param orphaned: if True creates an orphaned branch :param changeto: if True changes to the new branch after creation :param directory: directory in which to preform this action :raises: subprocess.CalledProcessError if any git calls fail """ current_branch = get_current_branch(directory) try: if orphaned: execute_command('git symbolic-ref HEAD refs/heads/' + branch, cwd=directory) execute_command('rm -f .git/index', cwd=directory) execute_command('git clean -fdx', cwd=directory) cmd = 'git commit --allow-empty -m "Created orphaned branch '\ '{0}"'.format(branch) execute_command(cmd, cwd=directory) if changeto: current_branch = None else: execute_command('git branch {0}'.format(branch), cwd=directory) if changeto: checkout(branch, directory=directory) current_branch = None finally: if current_branch is not None: checkout(current_branch, directory=directory) def ensure_git_root(): """ Checks that you are in the root of the git repository, else exit. :raises: SystemExit if this is not a valid git repository. :raises: SystemExit if not in the root of a git repository. """ root = get_root() if root is None: error("Not in a git repository.", exit=True) if os.getcwd() != root: error("Must call from the top folder of the git repository", exit=True) def get_root(directory=None): """ Returns the git root directory above the given dir. If the given dir is not in a git repository, None is returned. :param directory: directory to query from, if None the cwd is used :returns: root of git repository or None if not a git repository """ cmd = 'git rev-parse --show-toplevel' try: output = check_output(cmd, shell=True, cwd=directory, stderr=PIPE) except CalledProcessError: return None return output.strip() def get_current_branch(directory=None): """ Returns the current git branch by parsing the output of `git branch` This will raise a RuntimeError if the current working directory is not a git repository. If no branch could be determined it will return None, i.e. (no branch) will return None. :param directory: directory in which to run the command :returns: current git branch or None if none can be determined, (no branch) :raises: subprocess.CalledProcessError if git command fails """ cmd = 'git branch --no-color' output = check_output(cmd, shell=True, cwd=directory) output = output.splitlines() for token in output: if token.strip().startswith('*'): token = token[2:] if token == '(no branch)': return None return token return None def track_branches(branches=None, directory=None): """ Tracks all specified branches. :param branches: a list of branches that are to be tracked if not already tracked. If this is set to None then all remote branches will be tracked. :param directory: directory in which to run all commands :raises: subprocess.CalledProcessError if git command fails """ if type(branches) == str: branches = [branches] debug("track_branches(" + str(branches) + ", " + str(directory) + ")") if branches == []: return # Save the current branch current_branch = get_current_branch(directory) try: # Get the local branches local_branches = get_branches(local_only=True, directory=directory) # Get the remote and local branches all_branches = get_branches(local_only=False, directory=directory) # Calculate the untracked branches untracked_branches = [] for branch in all_branches: if branch.startswith('remotes/'): if branch.count('/') >= 2: branch = '/'.join(branch.split('/')[2:]) if branch not in local_branches: untracked_branches.append(branch) # Prune any untracked branches by specified branches if branches is not None: branches_to_track = [] for untracked in untracked_branches: if untracked in branches: branches_to_track.append(untracked) else: branches_to_track = untracked_branches # Track branches debug("Tracking branches: " + str(branches_to_track)) for branch in branches_to_track: checkout(branch, directory=directory) finally: if current_branch: checkout(current_branch, directory=directory) def get_last_tag_by_version(directory=None): """ Returns the most recent, by date, tag in the given local git repository. :param directory: the directory in which to run the query :returns: the most recent tag by date, else '' if there are no tags :raises: subprocess.CalledProcessError if git command fails """ cmd = "git for-each-ref --sort='*authordate' " \ "--format='%(refname:short)' refs/tags/upstream" output = check_output(cmd, shell=True, cwd=directory, stderr=PIPE) tags = [] versions = [] for line in output.splitlines(): tags.append(line.strip()) versions.append(parse_version(line.strip())) return tags[versions.index(max(versions))] if versions else '' def get_last_tag_by_date(directory=None): """ Returns the most recent, by date, tag in the given local git repository. :param directory: the directory in which to run the query :returns: the most recent tag by date, else '' if there are no tags :raises: subprocess.CalledProcessError if git command fails """ cmd = "git for-each-ref --sort='*authordate' " \ "--format='%(refname:short)' refs/tags/upstream" output = check_output(cmd, shell=True, cwd=directory, stderr=PIPE) output = output.splitlines() if len(output) == 0: return '' return output[-1] def get_remotes(directory=None): """ Returns a list of remote names. :param directory: directory to list remotes from :returns: a list of git remotes :raises: RuntimeError if directory is not a git repository """ root = get_root(directory) checked_dir = directory or os.getcwd() if root is None: raise RuntimeError("Directory '{0}' is not in a git repository.".format(checked_dir)) cmd = "git remote -v" output = check_output(cmd, shell=True, cwd=root, stderr=PIPE) return list(set([x.split()[0].strip() for x in output.splitlines() if x.strip()])) bloom-0.10.7/bloom/github.py000066400000000000000000000324321403641720100156550ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Provides functions for interating with github """ from __future__ import print_function import base64 import datetime import getpass import json import os import socket import sys from bloom.logging import error from bloom.logging import info from bloom.logging import warning from bloom.util import maybe_continue from bloom.util import safe_input try: # Python2 from urllib import urlencode from urllib2 import HTTPError from urllib2 import Request, urlopen from urllib2 import URLError from urlparse import urlparse from urlparse import urlunsplit except ImportError: # Python3 from urllib.error import HTTPError from urllib.error import URLError from urllib.parse import urlparse from urllib.parse import urlunsplit from urllib.request import Request, urlopen import bloom def auth_header_from_basic_auth(user, password): auth_str = '{0}:{1}'.format(user, password) if sys.version_info >= (3, 0): auth_str = auth_str.encode() b64_encoded = base64.b64encode(auth_str) if sys.version_info >= (3, 0): b64_encoded = b64_encoded.decode() return "Basic {0}".format(b64_encoded) def auth_header_from_token(username, token): # Handle new GitHub personal access tokens # which are used with basic authentication. if token.startswith('ghp_'): return auth_header_from_basic_auth(username, token) else: return auth_header_from_oauth_token(token) def auth_header_from_oauth_token(token): return "token " + token def get_bloom_headers(auth=None): headers = {} headers['Content-Type'] = "application/json;charset=utf-8" headers['User-Agent'] = 'bloom ' + bloom.__version__ if auth: headers['Authorization'] = auth return headers def do_github_get_req(path, auth=None, site='api.github.com'): return do_github_post_req(path, None, auth, site) def do_github_post_req(path, data=None, auth=None, site='api.github.com'): headers = get_bloom_headers(auth) url = urlunsplit(['https', site, path, '', '']) if data is None: request = Request(url, headers=headers) # GET else: data = json.dumps(data) if sys.version_info[0] >= 3: data = data.encode('utf-8') request = Request(url, data=data, headers=headers) # POST try: response = urlopen(request, timeout=120) except HTTPError as e: if e.code in [401]: raise GitHubAuthException(str(e) + ' (%s)' % url) else: raise GithubException(str(e) + ' (%s)' % url) except URLError as e: raise GithubException(str(e) + ' (%s)' % url) return response def json_loads(resp): """Handle parsing json from an HTTP response for both Python 2 and Python 3.""" try: charset = resp.headers.getparam('charset') charset = 'utf8' if not charset else charset except AttributeError: charset = resp.headers.get_content_charset() return json.loads(resp.read().decode(charset)) class GithubException(Exception): def __init__(self, msg, resp=None): if resp: msg = "{msg}: {resp.getcode()}".format(**locals()) else: msg = "{msg}: {resp}".format(**locals()) super(GithubException, self).__init__(msg) if resp: self.resp = resp class GitHubAuthException(GithubException): def __init__(self, msg): super(GithubException, self).__init__(msg) class Github(object): def __init__(self, username, auth, token=None): self.username = username self.auth = auth self.token = token def check_token_validity(self, username, token, update_auth=False): resp = do_github_get_req('/user', self.auth) resp_data = json_loads(resp) if resp.getcode() != 200 or 'login' not in resp_data: raise GithubException('Token authorization unsuccessful', resp) if update_auth: self.username = username self.token = token def get_repo(self, owner, repo): resp = do_github_get_req('/repos/{owner}/{repo}'.format(**locals()), auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200']: raise GithubException( "Failed to get information for repository '{owner}/{repo}'".format(**locals()), resp) resp_data = json_loads(resp) return resp_data def list_repos(self, user, start_page=None): page = start_page or 1 repos = [] while True: url = '/users/{user}/repos?page={page}'.format(**locals()) resp = do_github_get_req(url, auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200']: raise GithubException( "Failed to list repositories for user '{user}' using url '{url}'".format(**locals()), resp) new_repos = json_loads(resp) if not new_repos: return repos repos.extend(new_repos) page += 1 def get_branch(self, owner, repo, branch): url = '/repos/{owner}/{repo}/branches/{branch}'.format(**locals()) resp = do_github_get_req(url, auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200']: raise GithubException("Failed to get branch information for '{branch}' on '{owner}/{repo}' using '{url}'" .format(**locals()), resp) return json_loads(resp) def list_branches(self, owner, repo, start_page=None): page = start_page or 1 branches = [] while True: url = '/repos/{owner}/{repo}/branches?page={page}&per_page=2'.format(**locals()) resp = do_github_get_req(url, auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200']: raise GithubException( "Failed to list branches for '{owner}/{repo}' using url '{url}'".format(**locals()), resp) new_branches = json_loads(resp) if not new_branches: return branches branches.extend(new_branches) page += 1 def create_fork(self, parent_org, parent_repo): resp = do_github_post_req('/repos/{parent_org}/{parent_repo}/forks'.format(**locals()), {}, auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200', '202']: raise GithubException( "Failed to create a fork of '{parent_org}/{parent_repo}'".format(**locals()), resp) return json_loads(resp) def list_forks(self, org, repo, start_page=None): page = start_page or 1 forks = [] while True: url = '/repos/{org}/{repo}/forks?page={page}'.format(**locals()) resp = do_github_get_req(url, auth=self.auth) if '{0}'.format(resp.getcode()) not in ['200', '202']: raise GithubException( "Failed to list forks of '{org}/{repo}'".format(**locals()), resp) new_forks = json_loads(resp) if not new_forks: return forks forks.extend(new_forks) page += 1 def create_pull_request(self, org, repo, branch, fork_org, fork_branch, title, body=""): data = { 'title': title, 'body': body, 'head': "{0}:{1}".format(fork_org, fork_branch), 'base': branch } resp = do_github_post_req('/repos/{org}/{repo}/pulls'.format(**locals()), data, self.auth) if '{0}'.format(resp.getcode()) not in ['200', '201']: raise GithubException("Failed to create pull request", resp) resp_json = json_loads(resp) return resp_json['html_url'] def get_gh_info(url): o = urlparse(url) if 'raw.github.com' not in o.netloc and 'raw.githubusercontent.com' not in o.netloc: return None url_paths = o.path.split('/') if len(url_paths) < 5: return None return {'server': 'github.com', 'org': url_paths[1], 'repo': url_paths[2], 'branch': url_paths[3], 'path': '/'.join(url_paths[4:])} _gh = None def get_github_interface(quiet=False): def mfa_prompt(oauth_config_path, username): """Explain how to create a token for users with Multi-Factor Authentication configured.""" warning("Receiving 401 when trying to create an oauth token can be caused by the user " "having two-factor authentication enabled.") warning("If 2FA is enabled, the user will have to create an oauth token manually.") warning("A token can be created at https://github.com/settings/tokens") warning("The resulting token can be placed in the '{oauth_config_path}' file as such:" .format(**locals())) info("") warning('{{"github_user": "{username}", "oauth_token": "TOKEN_GOES_HERE"}}' .format(**locals())) info("") global _gh if _gh is not None: return _gh # First check to see if the oauth token is stored oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom') config = {} if os.path.exists(oauth_config_path): with open(oauth_config_path, 'r') as f: config = json.loads(f.read()) token = config.get('oauth_token', None) username = config.get('github_user', None) if token and username: return Github(username, auth=auth_header_from_token(username, token), token=token) if not os.path.isdir(os.path.dirname(oauth_config_path)): os.makedirs(os.path.dirname(oauth_config_path)) if quiet: return None # Ok, now we have to ask for the user name and pass word info("") warning("Looks like bloom doesn't have an oauth token for you yet.") warning("You can create a token by visiting https://github.com/settings/tokens in your browser.") warning("For bloom to work the token must have at least `public_repo` scope.") warning("If you want bloom to be able to automatically update your fork of ros/rosdistro (recommended)") warning("then you must also enable the workflow scope for the token.") warning("If you need to unauthorize it, remove it from the 'Tokens' menu in your GitHub account settings.") info("") if not maybe_continue('y', 'Would you like to enter an access token now'): return None token = None while token is None: try: username = getpass.getuser() username = safe_input("GitHub username [{0}]: ".format(username)) or username token = getpass.getpass("GitHub access token: ").strip() except (KeyboardInterrupt, EOFError): return None if not token: error("No token was given, aborting.") return None gh = Github(username, auth=auth_header_from_token(username, token)) try: gh.check_token_validity(username, token, update_auth=True) with open(oauth_config_path, 'w') as f: config.update({'oauth_token': token, 'github_user': username}) f.write(json.dumps(config)) info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'" .format(**locals())) except GitHubAuthException as exc: error("{0}".format(exc)) mfa_prompt(oauth_config_path, username) except GithubException as exc: error("{0}".format(exc)) info("") if hasattr(exc, 'resp') and '{0}'.format(exc.resp.status) in ['401']: mfa_prompt(oauth_config_path, username) warning("This sometimes fails when the username or password are incorrect, try again?") if not maybe_continue(): return None _gh = gh return gh bloom-0.10.7/bloom/logging.py000077500000000000000000000250031403641720100160200ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from __future__ import unicode_literals import atexit import datetime import os from platform import mac_ver try: from pkg_resources import parse_version except OSError: os.chdir(os.path.expanduser('~')) from pkg_resources import parse_version import re import string import sys import functools _ansi = {} _quiet = False _debug = False _log_prefix_stack = [''] _log_prefix = '' _log_indent = False _drop_first_log_prefix = False _emoji_check_mark = "✅ " _emoji_cross_mark = "❌ " _is_mac_lion_or_greater = parse_version(mac_ver()[0]) >= parse_version('10.7.0') def ansi(key): """Returns the escape sequence for a given ansi color key""" global _ansi return _ansi[key] _strip_ansi_re = re.compile(r'\033\[[0-9;m]*') def strip_ansi(msg): return _strip_ansi_re.sub('', msg) def enable_ANSI_colors(): """ Populates the global module dictionary `ansi` with ANSI escape sequences. """ global _ansi color_order = [ 'black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white' ] short_colors = { 'black': 'k', 'red': 'r', 'green': 'g', 'yellow': 'y', 'blue': 'b', 'purple': 'p', 'cyan': 'c', 'white': 'w' } _ansi = { 'escape': '\033', 'reset': 0, '|': 0, 'boldon': 1, '!': 1, 'italicson': 3, '/': 3, 'ulon': 4, '_': 4, 'invon': 7, 'boldoff': 22, 'italicsoff': 23, 'uloff': 24, 'invoff': 27 } # Convert plain numbers to escapes for key in _ansi: if key != 'escape': _ansi[key] = '{0}[{1}m'.format(_ansi['escape'], _ansi[key]) # Foreground for index, color in enumerate(color_order): _ansi[color] = '{0}[{1}m'.format(_ansi['escape'], 30 + index) _ansi[color + 'f'] = _ansi[color] _ansi[short_colors[color] + 'f'] = _ansi[color + 'f'] # Background for index, color in enumerate(color_order): _ansi[color + 'b'] = '{0}[{1}m'.format(_ansi['escape'], 40 + index) _ansi[short_colors[color] + 'b'] = _ansi[color + 'b'] # Fmt sanitizers _ansi['atexclimation'] = '@!' _ansi['atfwdslash'] = '@/' _ansi['atunderscore'] = '@_' _ansi['atbar'] = '@|' def disable_ANSI_colors(): """ Sets all the ANSI escape sequences to empty strings, effectively disabling console colors. """ global _ansi for key in _ansi: _ansi[key] = '' def is_mac_lion_or_greater(): global _is_mac_lion_or_greater return _is_mac_lion_or_greater def get_success_prefix(): return _emoji_check_mark if _is_mac_lion_or_greater else "@{gf}<== @|" def get_error_prefix(): return _emoji_cross_mark if _is_mac_lion_or_greater else "@{rf}@!<== @|" # Default to ansi colors on enable_ANSI_colors() def quiet(state=True): global _quiet _quiet = state # Default to quiet off quiet(False) def enable_debug(state=True): global _debug _debug = state def is_debug(): global _debug return _debug # Default to debug off enable_debug('DEBUG' in os.environ) def enable_debug_indent(state=True): global _log_indent _log_indent = state enable_debug_indent(True) def enable_drop_first_log_prefix(state=True): global _drop_first_log_prefix _drop_first_log_prefix = state enable_drop_first_log_prefix(True) def _get_log_prefix(): global _log_prefix_stack, _log_indent if _log_indent: return (' ' * (len(_log_prefix_stack) - 2)) + _log_prefix_stack[-1] else: return _log_prefix_stack[-1] def push_log_prefix(prefix): global _log_prefix, _log_prefix_stack, _drop_first_log_prefix if _drop_first_log_prefix and len(_log_prefix_stack) <= 1: _log_prefix_stack.append('') else: _log_prefix_stack.append(prefix) _log_prefix = _get_log_prefix() def pop_log_prefix(): global _log_prefix, _log_prefix_stack if len(_log_prefix_stack) > 1: _log_prefix_stack.pop() _log_prefix = _get_log_prefix() class ContextDecorator(object): def __call__(self, f): @functools.wraps(f) def decorated(*args, **kwds): with self: return f(*args, **kwds) return decorated class log_prefix(ContextDecorator): def __init__(self, prefix): self.prefix = prefix def __enter__(self): push_log_prefix(self.prefix) def __exit__(self, exc_type, exc_value, traceback): pop_log_prefix() _file_log = None def debug(msg, file=None, end='\n', use_prefix=True): file = file if file is not None else sys.stdout global _quiet, _debug, _log_prefix, _file_log msg = '{0}'.format(msg) if use_prefix: msg = ansi('greenf') + _log_prefix + msg + ansi('reset') else: msg = ansi('greenf') + msg + ansi('reset') if not _quiet and _debug: print(msg, file=file, end=end) if _file_log is not None: print(('[debug] ' + strip_ansi(msg)).encode('UTF-8'), file=_file_log, end=end) return msg def info(msg, file=None, end='\n', use_prefix=True): file = file if file is not None else sys.stdout global _quiet, _log_prefix, _file_log msg = '{0}'.format(msg) if use_prefix: msg = _log_prefix + msg + ansi('reset') if not _quiet: print(msg, file=file, end=end) if _file_log is not None: print(('[info] ' + strip_ansi(msg)).encode('UTF-8'), file=_file_log, end=end) return msg def warning(msg, file=None, end='\n', use_prefix=True): file = file if file is not None else sys.stdout global _quiet, _log_prefix, _file_log msg = '{0}'.format(msg) if use_prefix: msg = ansi('yellowf') + _log_prefix + msg \ + ansi('reset') else: msg = ansi('yellowf') + msg + ansi('reset') if not _quiet: print(msg, file=file, end=end) if _file_log is not None: print(('[warning] ' + strip_ansi(msg)).encode('UTF-8'), file=_file_log, end=end) return msg def error(msg, file=None, end='\n', use_prefix=True, exit=False): file = file if file is not None else sys.stderr global _quiet, _log_prefix, _file_log msg = '{0}'.format(msg) if use_prefix: msg = ansi('redf') + ansi('boldon') + _log_prefix + msg + ansi('reset') else: msg = ansi('redf') + ansi('boldon') + msg + ansi('reset') if _file_log is not None: print(('[error] ' + strip_ansi(msg)).encode('UTF-8'), file=_file_log, end=end) if exit: if _file_log is not None: print("[error] SYS.EXIT", file=_file_log, end=end) sys.exit(msg) if not _quiet: print(msg, file=file, end=end) return msg try: _log_id = os.environ.get('BLOOM_LOGGING_ID', str(os.getpid())) os.environ['BLOOM_LOGGING_ID'] = _log_id # Store in env for subprocess _file_log_prefix = os.path.join(os.path.expanduser('~'), '.bloom_logs') if not os.path.isdir(_file_log_prefix): os.makedirs(_file_log_prefix) _file_log_path = os.path.join(_file_log_prefix, _log_id + '.log') _file_log = open(_file_log_path, 'a') if str(os.getpid()) == _log_id: import bloom _file_log.write("[bloom] bloom version " + bloom.__version__ + "\n") except Exception as exc: _file_log = None error("Logging is not working: {0}: {1}".format(exc.__class__.__name__, exc)) _summary_file = None def _get_summary_file_path(): global _summary_file, _file_log_prefix, _log_id if _summary_file is None: _summary_file = os.path.join(_file_log_prefix, _log_id + '.summary') return _summary_file @atexit.register def close_logging(): global _file_log, _summary_file if _file_log is not None: name = _file_log.name _file_log.close() _file_log = None if str(os.getpid()) == os.path.basename(name).split('.')[0]: new_name = str(datetime.datetime.now()) new_name = new_name.replace(' ', '_').replace(':', '.') + '.log' new_name = os.path.basename(str(sys.argv[0])) + "_" + new_name new_name = os.path.join(os.path.dirname(name), new_name) os.rename(name, new_name) if os.path.exists(name): os.remove(name) class ColorTemplate(string.Template): delimiter = '@' def sanitize(msg): """Sanitizes the existing msg, use before adding color annotations""" msg = msg.replace('@', '@@') msg = msg.replace('{', '{{') msg = msg.replace('}', '}}') msg = msg.replace('@@!', '@{atexclimation}') msg = msg.replace('@@/', '@{atfwdslash}') msg = msg.replace('@@_', '@{atunderscore}') msg = msg.replace('@@|', '@{atbar}') return msg def fmt(msg): """Replaces color annotations with ansi escape sequences""" global _ansi msg = msg.replace('@!', '@{boldon}') msg = msg.replace('@/', '@{italicson}') msg = msg.replace('@_', '@{ulon}') msg = msg.replace('@|', '@{reset}') t = ColorTemplate(msg) msg = t.substitute(_ansi) + ansi('reset') msg = msg.replace('{{', '{') msg = msg.replace('}}', '}') return msg bloom-0.10.7/bloom/packages.py000066400000000000000000000114041403641720100161450ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Provides common utility functions for bloom. """ from __future__ import print_function import os import sys import traceback from bloom.git import show from bloom.config import BLOOM_CONFIG_BRANCH from bloom.logging import debug from bloom.logging import error from bloom.logging import info from bloom.logging import warning try: from catkin_pkg.packages import find_packages from catkin_pkg.packages import verify_equal_package_versions except ImportError: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", file=sys.stderr, exit=True) def get_ignored_packages(release_directory=None): prefix = os.environ.get('BLOOM_TRACK', 'packages') data = show(BLOOM_CONFIG_BRANCH, '{0}.ignored'.format(prefix), directory=release_directory) or '' return [p.strip() for p in data.split() if p.strip()] def get_package_data(branch_name=None, directory=None, quiet=True, release_directory=None): """ Gets package data about the package(s) in the current branch. It also ignores the packages in the `packages.ignore` file in the master branch. :param branch_name: name of the branch you are searching on (log use only) """ log = debug if quiet else info repo_dir = directory or os.getcwd() if branch_name: log("Looking for packages in '{0}' branch... ".format(branch_name), end='') else: log("Looking for packages in '{0}'... ".format(directory or os.getcwd()), end='') # Check for package.xml(s) packages = find_packages(repo_dir) if type(packages) == dict and packages != {}: if len(packages) > 1: log("found " + str(len(packages)) + " packages.", use_prefix=False) else: log("found '" + list(packages.values())[0].name + "'.", use_prefix=False) ignored_packages = get_ignored_packages(release_directory=release_directory) for k, v in dict(packages).items(): # Check for packages with upper case names if v.name.lower() != v.name: error("Cowardly refusing to release packages with uppercase characters in the name: " + v.name) error("See:") error(" https://github.com/ros-infrastructure/bloom/issues/191") error(" https://github.com/ros-infrastructure/bloom/issues/76") error("Invalid package names, aborting.", exit=True) # Check for ignored packages if v.name in ignored_packages: warning("Explicitly ignoring package '{0}' because it is in the `{1}.ignored` file." .format(v.name, os.environ.get('BLOOM_TRACK', 'packages'))) del packages[k] if packages == {}: error("All packages that were found were also ignored, aborting.", exit=True) version = verify_equal_package_versions(packages.values()) return [p.name for p in packages.values()], version, packages # Otherwise we have a problem log("failed.", use_prefix=False) error("No package.xml(s) found, and '--package-name' not given, aborting.", use_prefix=False, exit=True) bloom-0.10.7/bloom/rosdistro_api.py000066400000000000000000000170151403641720100172540ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2014, Open Source Robotics Foundation, Inc. # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function from __future__ import unicode_literals import os import sys import traceback from pkg_resources import parse_version # python2/3 compatibility try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse from bloom.github import Github from bloom.github import GithubException from bloom.github import get_gh_info from bloom.github import get_github_interface from bloom.logging import debug from bloom.logging import error from bloom.logging import info try: import rosdistro if parse_version(rosdistro.__version__) < parse_version('0.7.0'): error("rosdistro version 0.7.0 or greater is required, found '{0}' from '{1}'." .format(rosdistro.__version__, os.path.dirname(rosdistro.__file__)), exit=True) except ImportError: debug(traceback.format_exc()) error("rosdistro was not detected, please install it.", file=sys.stderr, exit=True) _rosdistro_index = None _rosdistro_distribution_files = {} _rosdistro_index_commit = None _rosdistro_index_original_branch = None def get_index_url(): global _rosdistro_index_commit, _rosdistro_index_original_branch index_url = rosdistro.get_index_url() pr = urlparse(index_url) if pr.netloc in ['raw.github.com', 'raw.githubusercontent.com']: # Try to determine what the commit hash was tokens = [x for x in pr.path.split('/') if x] if len(tokens) <= 3: debug("Failed to get commit for rosdistro index file: index url") debug(tokens) return index_url owner = tokens[0] repo = tokens[1] branch = tokens[2] gh = get_github_interface(quiet=True) if gh is None: # Failed to get it with auth, try without auth (may fail) gh = Github(username=None, auth=None) try: data = gh.get_branch(owner, repo, branch) except GithubException: debug(traceback.format_exc()) debug("Failed to get commit for rosdistro index file: api") return index_url _rosdistro_index_commit = data.get('commit', {}).get('sha', None) if _rosdistro_index_commit is not None: info("ROS Distro index file associate with commit '{0}'" .format(_rosdistro_index_commit)) # Also mutate the index_url to use the commit (rather than the moving branch name) base_info = get_gh_info(index_url) base_branch = base_info['branch'] rosdistro_index_commit = _rosdistro_index_commit # Copy global into local for substitution middle = "{org}/{repo}".format(**base_info) index_url = index_url.replace("{pr.netloc}/{middle}/{base_branch}/".format(**locals()), "{pr.netloc}/{middle}/{rosdistro_index_commit}/".format(**locals())) info("New ROS Distro index url: '{0}'".format(index_url)) _rosdistro_index_original_branch = base_branch else: debug("Failed to get commit for rosdistro index file: json") return index_url def get_index(): global _rosdistro_index if _rosdistro_index is None: _rosdistro_index = rosdistro.get_index(get_index_url()) if _rosdistro_index.version == 1: error("This version of bloom does not support rosdistro version " "'{0}', please use an older version of bloom." .format(_rosdistro_index.version), exit=True) if _rosdistro_index.version > 4: error("This version of bloom does not support rosdistro version " "'{0}', please update bloom.".format(_rosdistro_index.version), exit=True) return _rosdistro_index def list_distributions(): return sorted(get_index().distributions.keys()) def get_distribution_type(distro): return get_index().distributions[distro].get('distribution_type') def get_python_version(distro): return get_index().distributions[distro].get('python_version') def get_most_recent(thing_name, repository, reference_distro): reference_distro_type = get_distribution_type(reference_distro) distros_with_entry = {} get_things = { 'release': lambda r: None if r.release_repository is None else r.release_repository, 'doc': lambda r: None if r.doc_repository is None else r.doc_repository, 'source': lambda r: None if r.source_repository is None else r.source_repository, } get_thing = get_things[thing_name] for distro in list_distributions(): # skip distros with a different type if the information is available if reference_distro_type is not None: if get_distribution_type(distro) != reference_distro_type: continue distro_file = get_distribution_file(distro) if repository in distro_file.repositories: thing = get_thing(distro_file.repositories[repository]) if thing is not None: distros_with_entry[distro] = thing # Choose the alphabetical last distro which contained a release of this repository default_distro = (sorted(distros_with_entry.keys()) or [None])[-1] default_thing = distros_with_entry.get(default_distro, None) return default_distro, default_thing def get_distribution_file(distro): global _rosdistro_distribution_files if distro not in _rosdistro_distribution_files: # REP 143, get list of distribution files and take the last one files = rosdistro.get_distribution_files(get_index(), distro) if not files: error("No distribution files listed for distribution '{0}'." .format(distro), exit=True) _rosdistro_distribution_files[distro] = files[-1] return _rosdistro_distribution_files[distro] def get_rosdistro_index_commit(): return _rosdistro_index_commit def get_rosdistro_index_original_branch(): return _rosdistro_index_original_branch bloom-0.10.7/bloom/summary.py000066400000000000000000000061531403641720100160710ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Open Source Robotics Foundation, Inc. nor # the names of its contributors may be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Implements the summarizing of actions on the master branch. """ from __future__ import print_function import atexit import os from bloom.logging import _get_summary_file_path from bloom.git import inbranch from bloom.git import get_root from bloom.git import has_changes from bloom.util import execute_command _summary_file = None def commit_summary(): global _summary_file if get_root() is None: return if _summary_file is None: return if not os.path.exists(_summary_file.name): return try: with inbranch('master'): readme_name = 'README.md' readme = '' if os.path.isfile(readme_name): with open(readme_name, 'r') as f: readme = f.read() _summary_file.close() with open(_summary_file.name, 'r') as f: readme = f.read() + "\n\n" + readme with open(readme_name, 'w') as f: f.write(readme) execute_command('git add ' + readme_name) if has_changes(): execute_command('git commit -m "Updating README.md"') finally: if _summary_file is not None: _summary_file.close() if os.path.exists(_summary_file.name): os.remove(_summary_file.name) def get_summary_file(): global _summary_file if _summary_file is None: _summary_file = open(_get_summary_file_path(), 'a') atexit.register(commit_summary) return _summary_file bloom-0.10.7/bloom/util.py000077500000000000000000000344201403641720100153520ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Provides common utility functions for bloom. """ from __future__ import print_function import argparse import os import shutil import socket import sys import tempfile import time try: # Python2 from urllib2 import HTTPError from urllib2 import URLError from urllib2 import urlopen except ImportError: # Python3 from urllib.error import HTTPError from urllib.error import URLError from urllib.request import urlopen from email.utils import formatdate from subprocess import CalledProcessError from subprocess import PIPE from subprocess import STDOUT from subprocess import Popen try: # Python2 from StringIO import StringIO except ImportError: # Python3 from io import StringIO from bloom.logging import debug from bloom.logging import disable_ANSI_colors from bloom.logging import enable_debug from bloom.logging import error from bloom.logging import fmt from bloom.logging import info from bloom.logging import sanitize from bloom.logging import warning try: to_unicode = unicode except NameError: to_unicode = str def flush_stdin(): try: from termios import tcflush, TCIFLUSH tcflush(sys.stdin, TCIFLUSH) except ImportError: # fallback if not supported on some platforms pass if sys.version_info < (3, 0): def safe_input(prompt=None): flush_stdin() return raw_input(prompt) else: def safe_input(prompt=None): flush_stdin() return input(prompt) # Convention: < 0 is a warning exit, 0 is normal, > 0 is an error class code(object): NOTHING_TO_DO = -10 OK = 0 UNKNOWN = 1 ANSWERED_NO_TO_CONTINUE = 5 NO_PACKAGE_XML_FOUND = 6 NOT_A_GIT_REPOSITORY = 10 NOT_ON_A_GIT_BRANCH = 11 GIT_HAS_LOCAL_CHANGES = 12 GIT_HAS_UNTRACKED_FILES = 13 BRANCH_DOES_NOT_EXIST = 14 INVALID_VERSION = 30 INVALID_UPSTREAM_TAG = 31 INVALID_BRANCH_ARGS = 40 VCSTOOLS_NOT_FOUND = 50 ROSDEP_NOT_FOUND = 51 EMPY_NOT_FOUND = 52 ROSDEP_FAILED = 53 COULD_NOT_GET_PATCH_INFO = 60 PATCHES_NOT_EXPORTED = 61 COULD_NOT_TRIM = 62 GENERATOR_MULTIPLE_PACKAGES_FOUND = 70 GENERATOR_UNRECOGNIZED_BUILD_TYPE = 71 GENERATOR_FAILED_TO_LOAD_TEMPLATE = 72 GENERATOR_NO_SUCH_ROSDEP_KEY = 73 GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO = 74 GENERATOR_INVALID_INSTALLER_KEY = 75 class change_directory(object): def __init__(self, directory=''): self.directory = directory self.original_cwd = None def __enter__(self): self.original_cwd = os.getcwd() os.chdir(self.directory) return self.directory def __exit__(self, exc_type, exc_value, traceback): if self.original_cwd and os.path.exists(self.original_cwd): os.chdir(self.original_cwd) class redirected_stdio(object): def __enter__(self): self.original_stdout = sys.stdout self.original_stderr = sys.stderr sys.stdout = out = StringIO() sys.stderr = err = StringIO() return out, err def __exit__(self, exc_type, exc_value, traceback): sys.stdout = self.original_stdout sys.stderr = self.original_stderr class temporary_directory(object): def __init__(self, prefix=''): self.prefix = prefix def __enter__(self): self.original_cwd = os.getcwd() self.temp_path = tempfile.mkdtemp(prefix=self.prefix) os.chdir(self.temp_path) return self.temp_path def __exit__(self, exc_type, exc_value, traceback): if self.temp_path and os.path.exists(self.temp_path): shutil.rmtree(self.temp_path) if self.original_cwd and os.path.exists(self.original_cwd): os.chdir(self.original_cwd) def get_rfc_2822_date(date): return formatdate(float(date.strftime("%s")), date.tzinfo) def load_url_to_file_handle(url, retry=2, retry_period=1, timeout=10): """Loads a given url with retries, retry_periods, and timeouts Based on https://github.com/ros-infrastructure/rosdistro/blob/master/src/rosdistro/loader.py :param url: URL to load and return contents of :type url: str :param retry: number of times to retry the url on 503 or timeout :type retry: int :param retry_period: time to wait between retries in seconds :type: retry_period: float :param timeout: timeout for opening the URL in seconds :type timeout: float """ try: fh = urlopen(url, timeout=timeout) except HTTPError as e: if e.code == 503 and retry: time.sleep(retry_period) return load_url_to_file_handle(url, retry=retry - 1, retry_period=retry_period, timeout=timeout) e.msg += ' (%s)' % url raise except URLError as e: if isinstance(e.reason, socket.timeout) and retry: time.sleep(retry_period) return load_url_to_file_handle(url, retry=retry - 1, retry_period=retry_period, timeout=timeout) raise URLError(str(e) + ' (%s)' % url) return fh def my_copytree(tree, destination, ignores=None): ignores = ignores or [] if os.path.exists(destination): if not os.path.isdir(destination): raise RuntimeError("Destination exists and is not a directory: '{0}'".format(destination)) else: os.makedirs(destination) for item in os.listdir(tree): if item in ignores: continue src = os.path.join(tree, item) dst = os.path.join(destination, item) if os.path.islink(src): linkto = os.readlink(src) os.symlink(linkto, dst) elif os.path.isdir(src): my_copytree(src, dst, ignores) elif os.path.isfile(src): shutil.copy(src, dst) else: raise RuntimeError("Unknown file type for element: '{0}'".format(src)) def add_global_arguments(parser): from bloom import __version__ group = parser.add_argument_group('global') add = group.add_argument add('-d', '--debug', help='enable debug messages', action='store_true', default=False) add('--pdb', help=argparse.SUPPRESS, action='store_true', default=False) add('--version', action='version', version=__version__, help="prints the bloom version") add('--no-color', action='store_true', default=False, dest='no_color', help=argparse.SUPPRESS) add('--quiet', help=argparse.SUPPRESS, default=False, action='store_true') add('--unsafe', default=False, action='store_true', help="Makes bloom faster, but if there is an error then you could run into trouble.") return parser _pdb = False _quiet = False _disable_git_clone = False _disable_git_clone_quiet = False _distro_list_prompt = [ 'indigo', 'kinetic', 'lunar', 'melodic', ] def disable_git_clone(state=True): global _disable_git_clone _disable_git_clone = state if state: os.environ['BLOOM_UNSAFE'] = "1" elif 'BLOOM_UNSAFE' in os.environ: del os.environ['BLOOM_UNSAFE'] def quiet_git_clone_warning(state=True): global _disable_git_clone_quiet _disable_git_clone_quiet = state if state: os.environ['BLOOM_UNSAFE_QUIET'] = "1" elif 'BLOOM_UNSAFE_QUIET' in os.environ: del os.environ['BLOOM_UNSAFE_QUIET'] def get_git_clone_state(): global _disable_git_clone return _disable_git_clone def get_distro_list_prompt(): global _distro_list_prompt return ', '.join(_distro_list_prompt) def get_git_clone_state_quiet(): global _disable_git_clone_quiet return _disable_git_clone_quiet def handle_global_arguments(args): global _pdb, _quiet enable_debug(args.debug or 'DEBUG' in os.environ) _pdb = args.pdb _quiet = args.quiet if args.no_color: disable_ANSI_colors() disable_git_clone(args.unsafe or 'BLOOM_UNSAFE' in os.environ) quiet_git_clone_warning('BLOOM_UNSAFE_QUIET' in os.environ) def print_exc(exc): exc_str = ''.join(exc) try: from pygments import highlight from pygments.lexers import PythonTracebackLexer from pygments.formatters import TerminalFormatter exc_str = highlight(exc_str, PythonTracebackLexer(), TerminalFormatter()) except ImportError: pass info(exc_str, file=sys.stderr, use_prefix=False) def custom_exception_handler(type, value, tb): global _pdb # Print traceback import traceback print_exc(traceback.format_exception(type, value, tb)) if not _pdb or hasattr(sys, 'ps1') or not sys.stderr.isatty(): pass else: # ...then start the debugger in post-mortem mode. import pdb pdb.set_trace() sys.excepthook = custom_exception_handler def pdb_hook(): global _pdb if _pdb: import pdb pdb.set_trace() def __get_env_for_cmd(cmd): executable = None if isinstance(cmd, list) or isinstance(cmd, tuple): executable = cmd[0] if isinstance(cmd, str) or isinstance(cmd, to_unicode): executable = cmd.split()[0] env = None if executable is not None and executable.startswith('git'): env = os.environ # If the output is from git, force lang to C to prevent output in different languages. env['LC_ALL'] = 'C' return env def check_output(cmd, cwd=None, stdin=None, stderr=None, shell=False): """Backwards compatible check_output""" env = __get_env_for_cmd(cmd) p = Popen(cmd, cwd=cwd, stdin=stdin, stderr=stderr, shell=shell, stdout=PIPE, env=env) out, err = p.communicate() if p.returncode: raise CalledProcessError(p.returncode, cmd) if not isinstance(out, str): out = out.decode('utf-8') return out def create_temporary_directory(prefix_dir=None): """Creates a temporary directory and returns its location""" from tempfile import mkdtemp return mkdtemp(prefix='bloom_', dir=prefix_dir) def maybe_continue(default='y', msg='Continue'): """Prompts the user for continuation""" default = default.lower() msg = "@!{msg} ".format(msg=sanitize(msg)) if default == 'y': msg += "@{yf}[Y/n]? @|" else: msg += "@{yf}[y/N]? @|" msg = fmt(msg) while True: response = safe_input(msg) if not response: response = default response = response.lower() if response not in ['y', 'n', 'q']: error_msg = 'Response `' + response + '` was not recognized, ' \ 'please use one of y, Y, n, N.' error(error_msg) else: break if response in ['n', 'q']: return False return True def extract_text(element): node_list = element.childNodes result = [] for node in node_list: if node.nodeType == node.TEXT_NODE: result.append(node.data) return ''.join(result) def segment_version(full_version): version_list = full_version.split('.') if len(version_list) != 3: warning('Invalid version element, expected: ' '..') if len(version_list) < 3: sys.exit(code.INVALID_VERSION) return version_list def execute_command(cmd, shell=True, autofail=True, silent=True, silent_error=False, cwd=None, return_io=False): """ Executes a given command using Popen. """ out_io = None err_io = None result = 0 if silent: out_io = PIPE err_io = STDOUT debug(((cwd) if cwd else os.getcwd()) + ":$ " + str(cmd)) env = __get_env_for_cmd(cmd) p = Popen(cmd, shell=shell, cwd=cwd, stdout=out_io, stderr=err_io, env=env) out, err = p.communicate() if out is not None and not isinstance(out, str): out = out.decode('utf-8') if err is not None and not isinstance(err, str): err = err.decode('utf-8') result = p.returncode if result != 0: if not silent_error: error("'execute_command' failed to call '{0}'".format(cmd) + " which had a return code ({0}):".format(result)) error("```") info(out, use_prefix=False) error("```") if autofail: raise CalledProcessError(result, cmd) if return_io: return result, out, err else: return result def get_versions_from_upstream_tag(tag): """ Returns the [major, minor, patch] version list given an upstream tag. """ tag_version = tag.split('/') if len(tag_version) != 2: error("Malformed tag {0}".format(tag)) sys.exit(code.INVALID_UPSTREAM_TAG) tag_version = tag_version[1] return segment_version(tag_version) bloom-0.10.7/docs/000077500000000000000000000000001403641720100136355ustar00rootroot00000000000000bloom-0.10.7/docs/.gitignore000066400000000000000000000000071403641720100156220ustar00rootroot00000000000000_build bloom-0.10.7/docs/Makefile000066400000000000000000000151461403641720100153040ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bloom.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bloom.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/bloom" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bloom" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." bloom-0.10.7/docs/bump_version.py000077500000000000000000000021421403641720100167210ustar00rootroot00000000000000#!/usr/bin/env python import argparse import re import sys if __name__ == '__main__': parser = argparse.ArgumentParser( description="Bumps the version of a given setup.py file" ) parser.add_argument('file', help="setup.py file to be bumped") parser.add_argument('--version_only', default=False, action='store_true') args = parser.parse_args() with open(args.file, 'r') as f: lines = f.read() version_line_regex = re.compile(".*version='\d*[.]\d*[.]\d*'.*") version_line = version_line_regex.findall(lines) version_line = version_line[0] version_regex = re.compile('\d*[.]\d*[.]\d*') version_str = version_regex.findall(version_line)[0] version_str = version_str.split('.') version_str[-1] = str(int(version_str[-1]) + 1) version_str = '.'.join(version_str) if args.version_only: print(version_str) sys.exit(0) new_version_line = version_regex.sub(version_str, version_line) for line in lines.splitlines(): if line.count("version='") > 0: print(new_version_line) else: print(line) bloom-0.10.7/docs/conf.py000066400000000000000000000217431403641720100151430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # bloom documentation build configuration file, created by # sphinx-quickstart on Tue Jan 21 22:47:50 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'bloom' copyright = u'2014, Open Source Robotics Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.4' # The full version, including alpha/beta/rc tags. setup_py = os.path.join(os.path.dirname(__file__), '..', 'setup.py') import subprocess release = subprocess.check_output(sys.executable + ' ' + setup_py + ' --version', shell=True).strip().decode('utf-8') print('Using release version: {0}'.format(release)) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] except ImportError: print("Building with default template because `sphinx_rtd_theme` is not available.") # otherwise, readthedocs.org uses their theme by default, so no need to specify it # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'bloomdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'bloom.tex', u'bloom Documentation', u'William Woodall', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'bloom', u'bloom Documentation', [u'William Woodall'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'bloom', u'bloom Documentation', u'William Woodall', 'bloom', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} bloom-0.10.7/docs/glossary.rst000066400000000000000000000031241403641720100162320ustar00rootroot00000000000000Glossary -------- .. glossary:: bloom.conf A file that lives on a special orphan branch `bloom` in a :term:`release repository` which contains bloom meta-information (like upstream repository location and type) and is used when making releases with bloom. wet A catkin-ized package. dry A non-catkin, rosbuild based software :term:`package` or stack. FHS The Linux `Filesystem Hierarchy Standard `_ release repository A git repository that bloom operates on, which is loosely based on :term:`git-buildpackage`. This repository contains snapshots of released upstream source trees, any patches needed to release the upstream project, and git tags which point to source trees setup for building platform specific packages (like debian source debs). git-buildpackage Suite to help with Debian packages in Git repositories. package A single software unit. In catkin a package is any folder containing a valid package.xml file. An upstream repository can have many packages, but a package must be completely contained in one repository. stack A term used by the ROS Fuerte version of catkin and the legacy rosbuild system. In the context of these systems, a stack is a software release unit with consists of zero to many ROS packages, which are the software build units, i.e. you release stacks, you build ROS packages. project CMake's notion of a buildable subdirectory: it contains a ``CMakeLists.txt`` that calls CMake's ``project()`` macro. bloom-0.10.7/docs/index.rst000077500000000000000000000036421403641720100155060ustar00rootroot00000000000000Bloom ===== .. Links .. _catkin: https://github.com/ros/catkin .. _bloom: http://ros.org/wiki/bloom What is bloom? -------------- Bloom is a release automation tool, designed to make generating platform specific release artifacts from source projects easier. Bloom is designed to work best with catkin_ projects, but can also accommodate other types of projects. How does it work? ----------------- Bloom works by importing your upstream source tree into a git repository, where it is manipulated and used to generate build artifacts for different platforms like Debian or Fedora. First bloom gathers information about your source repository and creates an archive for the version you want to release. Then the archive is imported into the release repository, and the source tree is run through a release track where it is tagged, can be patched, and has platform specific artifacts generated for it. The individual stages of these release tracks are tagged with git and those tags are used by build infrastructure and deployment systems. What can I release with bloom? ------------------------------ Bloom supports releasing arbitrary software packages, but is optimized for use with catkin_ projects. .. note:: For :term:`dry` ROS stacks, you should use the legacy `ros-release `_ system. How do I install bloom? ----------------------- On Ubuntu the recommended method is to use apt:: $ sudo apt-get install python-bloom On other systems you can install bloom via pypi:: $ sudo pip install -U bloom Note: pip will not notify you of updates, but bloom will notify you when you are using a version of bloom that is not the latest released. Develop and build from source:: $ python setup.py build $ sudo python setup.py develop How do I release something with bloom? --------------------------------------- Please refer to the documentation and tutorials on the bloom_ ROS wiki page. bloom-0.10.7/docs/make.bat000066400000000000000000000150531403641720100152460ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bloom.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bloom.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end bloom-0.10.7/scripts/000077500000000000000000000000001403641720100143745ustar00rootroot00000000000000bloom-0.10.7/scripts/README000066400000000000000000000001321403641720100152500ustar00rootroot00000000000000These are temporary scripts, the scripts which are installed are installed by setuptools. bloom-0.10.7/scripts/bloom-export-upstream000077500000000000000000000014031403641720100206050ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the pythonpath bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.export_upstream import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/bloom-generate000077500000000000000000000013741403641720100172270ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the pythonpath bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.generate import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/bloom-release000077500000000000000000000013651403641720100170550ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.release import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/bloom-update000077500000000000000000000013641403641720100167160ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.update import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-branch000077500000000000000000000013701403641720100174470ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.branch import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-config000077500000000000000000000013701403641720100174570ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.config import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-generate000077500000000000000000000013721403641720100200060ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.generate import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-import-upstream000077500000000000000000000014011403641720100213550ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.import_upstream import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-patch000077500000000000000000000014021403641720100173050ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.patch.patch_main import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/scripts/git-bloom-release000077500000000000000000000013711403641720100176330ustar00rootroot00000000000000#!/usr/bin/env python """ This is a place holder script for use in testing. The actual script which is installed is generated by python-setuptools. """ import os import sys # Prepend the bloom source directory to the path bloom_src = os.path.join(os.path.dirname(__file__), '..', 'bloom') if os.path.exists(bloom_src): sys.path.insert(0, os.path.abspath(os.path.dirname(bloom_src))) # Prepend the scripts directory to the path scripts_dir = os.path.join(os.path.dirname(__file__), '..', 'scripts') if os.path.exists(scripts_dir): os.environ['PATH'] = os.path.abspath(scripts_dir) + \ (':' + os.environ['PATH'] if 'PATH' in os.environ else '') from bloom.commands.git.release import main if __name__ == '__main__': sys.exit(main() or 0) bloom-0.10.7/setup.cfg000066400000000000000000000003441403641720100145270ustar00rootroot00000000000000[build_sphinx] source-dir = doc/ build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html # [nosetests] # where=test # with-coverage=0 # cover-package=nose # debug=nose.loader # pdb=0 # pdb-failures=0 bloom-0.10.7/setup.py000077500000000000000000000063121403641720100144240ustar00rootroot00000000000000#!/usr/bin/env python import sys from setuptools import find_packages, setup install_requires = [ 'catkin-pkg >= 0.4.3', 'setuptools', 'empy', 'python-dateutil', 'PyYAML', 'rosdep >= 0.15.0', 'rosdistro >= 0.8.0', 'vcstools >= 0.1.22', ] # argparse got moved into the stdlib in py2.7, so we only # need to install the pypi version if we're on an older # python. if sys.version_info[0] == 2 and sys.version_info[1] <= 6: install_requires.append('argparse') setup( name='bloom', version='0.10.7', packages=find_packages(exclude=['test', 'test.*']), package_data={ 'bloom.generators.debian': [ 'bloom/generators/debian/templates/*', 'bloom/generators/debian/templates/source/*' ], 'bloom.generators.rpm': [ 'bloom/generators/rpm/templates/*' ] }, include_package_data=True, install_requires=install_requires, author='Tully Foote, William Woodall', author_email='tfoote@openrobotics.org, william@openrobotics.org', maintainer='William Woodall', maintainer_email='william@openrobotics.org', url='http://www.ros.org/wiki/bloom', keywords=['ROS'], classifiers=['Programming Language :: Python', 'License :: OSI Approved :: BSD License'], description="Bloom is a release automation tool.", long_description="""\ Bloom provides tools for releasing software on top of a git repository \ and leverages tools and patterns from git-buildpackage. Additionally, \ bloom leverages meta and build information from catkin \ (https://github.com/ros/catkin) to automate release branching and the \ generation of platform specific source packages, like debian's src-debs.""", license='BSD', test_suite='test', entry_points={ 'console_scripts': [ 'git-bloom-config = bloom.commands.git.config:main', 'git-bloom-import-upstream = bloom.commands.git.import_upstream:main', 'git-bloom-branch = bloom.commands.git.branch:main', 'git-bloom-patch = bloom.commands.git.patch.patch_main:main', 'git-bloom-generate = bloom.commands.git.generate:main', 'git-bloom-release = bloom.commands.git.release:main', 'bloom-export-upstream = bloom.commands.export_upstream:main', 'bloom-update = bloom.commands.update:main', 'bloom-release = bloom.commands.release:main', 'bloom-generate = bloom.commands.generate:main' ], 'bloom.generators': [ 'release = bloom.generators.release:ReleaseGenerator', 'rosrelease = bloom.generators.rosrelease:RosReleaseGenerator', 'debian = bloom.generators.debian:DebianGenerator', 'rosdebian = bloom.generators.rosdebian:RosDebianGenerator', 'rpm = bloom.generators.rpm:RpmGenerator', 'rosrpm = bloom.generators.rosrpm:RosRpmGenerator' ], 'bloom.generate_cmds': [ 'debian = bloom.generators.debian.generate_cmd:description', 'rosdebian = bloom.generators.rosdebian:description', 'rpm = bloom.generators.rpm.generate_cmd:description', 'rosrpm = bloom.generators.rosrpm:description' ] } ) bloom-0.10.7/stdeb.cfg000077500000000000000000000014271403641720100144760ustar00rootroot00000000000000[DEFAULT] ; release with a high debinc to avoid conflict with upstream debian of the same release version Debian-Version: 100 Depends: python-yaml, python-empy, python-argparse, python-rosdep (>= 0.15.0), python-rosdistro (>= 0.8.0), python-vcstools (>= 0.1.22), python-setuptools, python-catkin-pkg (>= 0.4.3) Depends3: python3-yaml, python3-empy, python3-rosdep (>= 0.15.0), python3-rosdistro (>= 0.8.0), python3-vcstools (>= 0.1.22), python3-setuptools, python3-catkin-pkg (>= 0.4.3) Conflicts: python3-bloom Conflicts3: python-bloom Copyright-File: LICENSE.txt Suite: xenial yakkety zesty artful bionic cosmic disco eoan jessie stretch buster Suite3: xenial yakkety zesty artful bionic cosmic disco eoan focal jessie stretch buster Python2-Depends-Name: python X-Python3-Version: >= 3.4 bloom-0.10.7/test/000077500000000000000000000000001403641720100136645ustar00rootroot00000000000000bloom-0.10.7/test/__init__.py000066400000000000000000000010071403641720100157730ustar00rootroot00000000000000# Add the scripts folder to the path import os if 'PATH' in os.environ: scripts = os.path.join(os.path.dirname(__file__), '..', 'scripts') scripts = os.path.abspath(scripts) os.environ['PATH'] = scripts + ':' + os.environ['PATH'] user_email = 'test@example.com' user_name = 'Test User' os.environ.setdefault('GIT_AUTHOR_NAME', user_name) os.environ.setdefault('GIT_AUTHOR_EMAIL', user_email) os.environ.setdefault('GIT_COMMITTER_NAME', user_name) os.environ.setdefault('GIT_COMMITTER_EMAIL', user_email) bloom-0.10.7/test/system_tests/000077500000000000000000000000001403641720100164325ustar00rootroot00000000000000bloom-0.10.7/test/system_tests/__init__.py000066400000000000000000000000001403641720100205310ustar00rootroot00000000000000bloom-0.10.7/test/system_tests/common.py000066400000000000000000000012571403641720100203010ustar00rootroot00000000000000""" Common tools for system tests """ from __future__ import print_function import os from ..utils.common import bloom_answer from ..utils.common import change_directory from ..utils.common import user def create_release_repo(upstream_url, upstream_type, upstream_branch='', rosdistro='indigo'): user('mkdir foo_release') with change_directory('foo_release'): user('git init .') answers = ['y', 'foo', upstream_url, upstream_type, '', '', upstream_branch, rosdistro] with bloom_answer(answers): user('git-bloom-config new ' + str(rosdistro)) url = 'file://' + os.getcwd() return url bloom-0.10.7/test/system_tests/test_bloom_release.py000066400000000000000000000002141403641720100226500ustar00rootroot00000000000000from ..utils.common import user def test_bloom_release_dash_h(): assert 0 == user('bloom-release -h'), "Exited with non-zero status." bloom-0.10.7/test/system_tests/test_bloom_setup.py000066400000000000000000000074641403641720100224060ustar00rootroot00000000000000""" This system test tests the scenario of setting up a new bloom repository. """ import os import yaml from ..utils.common import in_temporary_directory from ..utils.common import bloom_answer from ..utils.common import user from bloom.config import BLOOM_CONFIG_BRANCH from bloom.git import branch_exists from bloom.git import inbranch @in_temporary_directory def test_create_a_bloom_repository(directory=None): """ Create a Bloom Repository: User creates a new release repository, and calls git-bloom-config new. actions: - user creates a folder - user calls 'git init .' in that folder - user calls 'git-bloom-config new ' expects: - bloom to ask the user if initialization is desired - bloom prompts the user for input on the settings - bloom branch to be created - tracks.yaml to be in bloom branch with appropriate contents """ # Setup user('mkdir new_repo') user('cd new_repo') user('git init .') # Test bloom command with bloom_answer(['', 'foo', 'https://github.com/bar/foo.git']): r = user('git-bloom-config new foo', return_io=True, silent=False) _, out, err = r assert out.count('ontinue') > 0, \ "git-bloom-config didn't ask about git init:\n```\n" + out + "\n```" assert branch_exists(BLOOM_CONFIG_BRANCH), \ "branch '{0}' does not exist".format(BLOOM_CONFIG_BRANCH) with inbranch(BLOOM_CONFIG_BRANCH): assert os.path.exists('tracks.yaml'), \ "no tracks.yaml file in the 'bloom' branch" with open('tracks.yaml', 'r') as f: tracks_dict = yaml.safe_load(f.read()) assert 'tracks' in tracks_dict, "bad bloom configurations" assert 'foo' in tracks_dict['tracks'], "bad bloom configurations" track = tracks_dict['tracks']['foo'] assert 'vcs_uri' in track, "bad bloom configurations" assert 'https://github.com/bar/foo.git' == track['vcs_uri'], \ "bad bloom configurations" + str(track) # Assert no question on the second attempt with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): user('git-bloom-config') @in_temporary_directory def test_call_config_outside_of_git_repo(directory=None): """ Call git-bloom-config Outside of a Git Repository: User calls git-bloom-config outside of a git repository. actions: - user calls 'git-bloom-config' expects: - git-bloom-config should error out with an appropriate message """ with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): r = user('git-bloom-config --quite new foo', auto_assert=False) assert r != 0, "actually returned " + str(r) def setup_git_repo(directory=None): user('mkdir new_repo') user('cd new_repo') user('git init .') user('touch README.md') user('git add README.md') user('git commit --allow-empty -m "Initial commit"') @in_temporary_directory def test_call_config_with_local_changes(directory=None): """ Call git-bloom-config on a git repository with local changes. """ setup_git_repo(directory) user('echo "some text" >> README.md') # Try to run bloom with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): r = user('git-bloom-config --quite new foo', auto_assert=False) assert r != 0, "actually returned " + str(r) @in_temporary_directory def test_call_config_with_untracked_files(directory=None): """ Call git-bloom-config on a git repository with untracked files. """ setup_git_repo(directory) user('echo "some text" > somefile.txt') # Try to run bloom with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): r = user('git-bloom-config --quite new foo', auto_assert=False) assert r != 0, "actually returned " + str(r) bloom-0.10.7/test/system_tests/test_catkin_release.py000066400000000000000000000442371403641720100230260ustar00rootroot00000000000000""" These system tests are testing the release of melodic+ catkin projects. """ from __future__ import print_function import os import re import sys try: from vcstools.vcs_abstraction import get_vcs_client except ImportError: print("vcstools was not detected, please install it.", file=sys.stderr) sys.exit(1) from .common import create_release_repo from ..utils.common import bloom_answer from ..utils.common import change_directory from ..utils.common import in_temporary_directory from ..utils.common import set_up_fake_rosdep from ..utils.common import user from ..utils.package_version import change_upstream_version from bloom.git import branch_exists from bloom.git import inbranch from bloom.util import code from bloom.commands.git.patch import export_cmd from bloom.commands.git.patch import import_cmd from bloom.commands.git.patch import remove_cmd from bloom.generators.debian.generator import sanitize_package_name def create_upstream_repository(packages, directory=None, format_versions=None): upstream_dir = 'upstream_repo_melodic' user('mkdir ' + upstream_dir) with change_directory(upstream_dir): user('git init .') user('echo "readme stuff" >> README.md') user('git add README.md') user('git commit -m "Initial commit" --allow-empty') user('git checkout -b melodic_devel') if format_versions is None: format_versions = [1] * len(packages) for package, format_version in zip(packages, format_versions): user('mkdir ' + package) with change_directory(package if len(packages) != 1 else '.'): package_xml = """\ {package} 0.1.0 A catkin (melodic) ROS package called '{package}' Bar BSD https://github.com/ros/this/issues https://github.com/ros/this {catkin_depend} <{depend_tag}>roscpp_core """.format(package=package, format_version=format_version, catkin_depend='catkin' if format_version > 1 else 'catkin\n catkin', depend_tag='depend' if format_version > 1 else 'run_depend', license_file_attr=' file="LICENSE"' if format_version > 2 else '') with open('package.xml', 'w+') as f: f.write(package_xml) user('touch .cproject') user('touch .project') user('touch white space.txt~') user('mkdir -p include/sym') user('touch include/{0}.h'.format(package)) os.symlink('../{0}.h'.format(package), 'include/sym/{0}.h'.format(package)) user('mkdir debian') user('touch debian/something.udev') user('echo "{0} license" > LICENSE'.format(package)) user('git add package.xml .cproject .project include debian "white space.txt~" LICENSE') user('git commit -m "Releasing version 0.1.0" --allow-empty') user('git tag 0.1.0 -m "Releasing version 0.1.0"') return os.getcwd() def _test_unary_package_repository(release_dir, version, directory=None, env=None): print("Testing in {0} at version {1}".format(release_dir, version)) with change_directory(release_dir): # First run everything with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): cmd = 'git-bloom-release{0} melodic' if 'BLOOM_VERBOSE' not in os.environ: cmd = cmd.format(' --quiet') else: cmd = cmd.format('') user(cmd, silent=False, env=env) ### ### Import upstream ### # does the upstream branch exist? assert branch_exists('upstream', local_only=True), "no upstream branch" # does the upstrea/ tag exist? ret, out, err = user('git tag', return_io=True) assert out.count('upstream/' + version) == 1, "no upstream tag created" # Is the package.xml from upstream in the upstream branch now? with inbranch('upstream'): assert os.path.exists('package.xml'), \ "upstream did not import: '" + os.getcwd() + "': " + \ str(os.listdir(os.getcwd())) assert os.path.exists(os.path.join('debian', 'something.udev')), \ "Lost the debian overlaid files in upstream branch" assert os.path.exists('white space.txt~'), \ "Lost file with whitespace in name in upstream branch" with open('package.xml') as f: package_xml = f.read() assert package_xml.count(version), "not right file" ### ### Release generator ### # patch import should have reported OK assert ret == code.OK, "actually returned ({0})".format(ret) # do the proper branches exist? assert branch_exists('release/melodic/foo'), \ "no release/melodic/foo branch" assert branch_exists('patches/release/melodic/foo'), \ "no patches/release/melodic/foo branch" # was the release tag created? ret, out, err = user('git tag', return_io=True) expected = 'release/melodic/foo/' + version + '-1' assert out.count(expected) == 1, \ "no release tag created, expected: '{0}'".format(expected) ### ### Make patch ### with inbranch('release/melodic/foo'): assert os.path.exists(os.path.join('debian', 'something.udev')), \ "Lost the debian overlaid files in release branch" assert os.path.exists('white space.txt~'), \ "Lost file with whitespace in name in release branch" assert os.path.islink('include/sym/foo.h'), "Symbolic link lost during pipeline" if os.path.exists('include/foo.h'): user('git rm include/foo.h') else: if not os.path.exists('include'): os.makedirs('include') user('touch include/foo.h') user('git add include/foo.h') user('git commit -m "A release patch" --allow-empty') ### ### Test import and export ### with inbranch('release/melodic/foo'): export_cmd.export_patches() remove_cmd.remove_patches() import_cmd.import_patches() ### ### Release generator, again ### # patch import should have reported OK assert ret == code.OK, "actually returned ({0})".format(ret) # do the proper branches exist? assert branch_exists('release/melodic/foo'), \ "no release/melodic/foo branch" assert branch_exists('patches/release/melodic/foo'), \ "no patches/release/melodic/foo branch" # was the release tag created? ret, out, err = user('git tag', return_io=True) assert out.count('release/melodic/foo/' + version) == 1, \ "no release tag created" @in_temporary_directory def test_unary_package_repository(directory=None): """ Release a single package catkin (melodic) repository. """ directory = directory if directory is not None else os.getcwd() # Initialize rosdep rosdep_dir = os.path.join(directory, 'foo_rosdep') env = dict(os.environ) fake_distros = {'melodic': {'ubuntu': ['bionic']}} fake_rosdeps = { 'catkin': {'ubuntu': []}, 'roscpp_core': {'ubuntu': []} } env.update(set_up_fake_rosdep(rosdep_dir, fake_distros, fake_rosdeps)) # Setup upstream_dir = create_upstream_repository(['foo'], directory) upstream_url = 'file://' + upstream_dir release_url = create_release_repo( upstream_url, 'git', 'melodic_devel', 'melodic') release_dir = os.path.join(directory, 'foo_release_clone') release_client = get_vcs_client('git', release_dir) assert release_client.checkout(release_url) versions = ['0.1.0', '0.1.1', '0.2.0'] import bloom.commands.git.release for index in range(len(versions)): _test_unary_package_repository(release_dir, versions[index], directory, env=env) bloom.commands.git.release.upstream_repos = {} if index != len(versions) - 1: change_upstream_version(upstream_dir, versions[index + 1]) @in_temporary_directory def test_multi_package_repository(directory=None): """ Release a multi package catkin (melodic) repository. """ directory = directory if directory is not None else os.getcwd() # Initialize rosdep rosdep_dir = os.path.join(directory, 'foo_rosdep') env = dict(os.environ) fake_distros = { 'melodic': { 'debian': ['stretch'], 'ubuntu': ['bionic'] } } fake_rosdeps = { 'catkin': {'debian': [], 'ubuntu': []}, 'roscpp_core': {'debian': [], 'ubuntu': []} } env.update(set_up_fake_rosdep(rosdep_dir, fake_distros, fake_rosdeps)) # Setup pkgs = ['foo', 'bar_ros', 'baz'] upstream_dir = create_upstream_repository(pkgs, directory, format_versions=[1, 2, 3]) upstream_url = 'file://' + upstream_dir release_url = create_release_repo( upstream_url, 'git', 'melodic_devel', 'melodic') release_dir = os.path.join(directory, 'foo_release_clone') release_client = get_vcs_client('git', release_dir) assert release_client.checkout(release_url) with change_directory(release_dir): # First run everything with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): cmd = 'git-bloom-release{0} melodic' if 'BLOOM_VERBOSE' not in os.environ: cmd = cmd.format(' --quiet') else: cmd = cmd.format('') user(cmd, silent=False, env=env) ### ### Import upstream ### # does the upstream branch exist? assert branch_exists('upstream', local_only=True), "no upstream branch" # does the upstrea/0.1.0 tag exist? ret, out, err = user('git tag', return_io=True) assert out.count('upstream/0.1.0') == 1, "no upstream tag created" # Is the package.xml from upstream in the upstream branch now? with inbranch('upstream'): for pkg in pkgs: with change_directory(pkg): assert os.path.exists( os.path.join('debian', 'something.udev')), \ "Lost the debian overlaid files in upstream branch" assert os.path.exists('white space.txt~'), \ "Lost file with whitespace in name in upstream branch" assert os.path.exists('package.xml'), \ "upstream did not import: " + os.listdir() with open('package.xml') as f: assert f.read().count('0.1.0'), "not right file" ### ### Release generator ### # Check the environment after the release generator ret, out, err = user('git tag', return_io=True) for pkg in pkgs: # Does the release/pkg branch exist? assert branch_exists('release/melodic/' + pkg), \ "no release/melodic/" + pkg + " branch" # Does the patches/release/pkg branch exist? assert branch_exists('patches/release/melodic/' + pkg), \ "no patches/release/melodic/" + pkg + " branch" # Did the release tag get created? assert out.count('release/melodic/' + pkg + '/0.1.0-1') == 1, \ "no release tag created for " + pkg # Is there a package.xml in the top level? with inbranch('release/melodic/' + pkg): assert os.path.exists('package.xml'), "release branch invalid" # Is it the correct package.xml for this pkg? package_xml = open('package.xml', 'r').read() assert package_xml.count('' + pkg + ''), \ "incorrect package.xml for " + str(pkg) # Make a patch with inbranch('release/melodic/' + pkgs[0]): user('echo "This is a change" >> README.md') user('git add README.md') user('git commit -m "added a readme" --allow-empty') ### ### Release generator, again ### with bloom_answer(bloom_answer.ASSERT_NO_QUESTION): ret = user('git-bloom-generate -y rosrelease melodic -s upstream', env=env) # patch import should have reported OK assert ret == code.OK, "actually returned ({0})".format(ret) # Check the environment after the release generator ret, out, err = user('git tag', return_io=True) for pkg in pkgs: # Does the release/pkg branch exist? assert branch_exists('release/melodic/' + pkg), \ "no release/melodic/" + pkg + " branch" # Does the patches/release/pkg branch exist? assert branch_exists('patches/release/melodic/' + pkg), \ "no patches/release/melodic/" + pkg + " branch" # Did the release tag get created? assert out.count('release/melodic/' + pkg + '/0.1.0-1') == 1, \ "no release tag created for " + pkg # Is there a package.xml in the top level? with inbranch('release/melodic/' + pkg): assert os.path.exists(os.path.join('debian', 'something.udev')), \ "Lost the debian overlaid files in release branch" assert os.path.exists('white space.txt~'), \ "Lost file with whitespace in name in release branch" assert os.path.exists('package.xml'), "release branch invalid" # Is it the correct package.xml for this pkg? with open('package.xml', 'r') as f: assert f.read().count('' + pkg + ''), \ "incorrect package.xml for " + str(pkg) ### ### ROSDebian Generator ### # Check the environment after the release generator ret, out, err = user('git tag', return_io=True) for pkg in pkgs: for distro in ['bionic', 'stretch']: pkg_san = sanitize_package_name(pkg) # Does the debian/distro/pkg branch exist? assert branch_exists('debian/melodic/' + distro + '/' + pkg), \ "no debian/melodic/" + pkg + " branch" # Does the patches/debian/distro/pkg branch exist? patches_branch = 'patches/debian/melodic/' + distro + '/' + pkg assert branch_exists(patches_branch), \ "no " + patches_branch + " branch" # Did the debian tag get created? tag = 'debian/ros-melodic-' + pkg_san + '_0.1.0-1_' + distro assert out.count(tag) == 1, \ "no '" + tag + "'' tag created for '" + pkg + "': `\n" + \ out + "\n`" # Is there a package.xml in the top level? with inbranch('debian/melodic/' + distro + '/' + pkg): assert os.path.exists( os.path.join('debian', 'something.udev')), \ "Lost the debian overlaid files in debian branch" assert os.path.exists('white space.txt~'), \ "Lost file with whitespace in name in debian branch" assert os.path.exists('package.xml'), "debian branch invalid" # Is there blank lins due to no Conflicts/Replaces? # See: https://github.com/ros-infrastructure/bloom/pull/329 with open(os.path.join('debian', 'control'), 'r') as f: assert f.read().count('\n\nHomepage:') == 0, \ "Extra blank line before Homepage detected." # Is it the correct package.xml for this pkg? with open('package.xml', 'r') as f: package_xml = f.read() assert package_xml.count('' + pkg + ''), \ "incorrect package.xml for " + str(pkg) format_version = int(re.search('format="(\d+)"', package_xml).group(1)) # Is there a copyright file for this pkg? if format_version <= 2: assert not os.path.exists( os.path.join('debian', 'copyright')), \ "debian/copyright should not be generated" else: with open('debian/copyright', 'r') as f: assert pkg + ' license' in f.read(), \ "debian/copyright does not include right license text" @in_temporary_directory def test_upstream_tag_special_tag(directory=None): """ Release a single package catkin (melodic) repository, first put an upstream tag into the release repository to test that bloom can handle it. """ directory = directory if directory is not None else os.getcwd() # Initialize rosdep rosdep_dir = os.path.join(directory, 'foo_rosdep') env = dict(os.environ) fake_distros = {'melodic': {'ubuntu': ['bionic']}} fake_rosdeps = { 'catkin': {'ubuntu': []}, 'roscpp_core': {'ubuntu': []} } env.update(set_up_fake_rosdep(rosdep_dir, fake_distros, fake_rosdeps)) # Setup upstream_dir = create_upstream_repository(['foo'], directory) upstream_url = 'file://' + upstream_dir release_url = create_release_repo( upstream_url, 'git', 'melodic_devel', 'melodic') release_dir = os.path.join(directory, 'foo_release_clone') release_client = get_vcs_client('git', release_dir) assert release_client.checkout(release_url) with change_directory(release_dir): user('git tag upstream/0.0.0@baz') import bloom.commands.git.release _test_unary_package_repository(release_dir, '0.1.0', directory, env=env) bloom-0.10.7/test/test_code_format.py000066400000000000000000000006461403641720100175650ustar00rootroot00000000000000import pep8 import os def test_pep8_conformance(): """Test source code for PEP8 conformance""" pep8style = pep8.StyleGuide(max_line_length=120) report = pep8style.options.report report.start() pep8style.input_dir(os.path.join(os.path.dirname(__file__), '..', 'bloom')) report.stop() assert report.total_errors == 0, "Found '{0}' code style errors (and warnings).".format(report.total_errors) bloom-0.10.7/test/unit_tests/000077500000000000000000000000001403641720100160655ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/__init__.py000066400000000000000000000000001403641720100201640ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_config.py000066400000000000000000000012321403641720100207410ustar00rootroot00000000000000import os from ..utils.common import AssertRaisesContext from ..utils.common import redirected_stdio from bloom.config import validate_track_versions test_data_dir = os.path.join(os.path.dirname(__file__), 'test_packages_data') def test_validate_track_versions(): tracks_dict = { 'tracks': { 'foo': { 'version': 'v1.2.7-rc1' } } } with AssertRaisesContext(ValueError, "it must be formatted as 'MAJOR.MINOR.PATCH'"): with redirected_stdio(): validate_track_versions(tracks_dict) tracks_dict['tracks']['foo']['version'] = '1.2.7' validate_track_versions(tracks_dict) bloom-0.10.7/test/unit_tests/test_generators/000077500000000000000000000000001403641720100212755ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/__init__.py000066400000000000000000000000001403641720100233740ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/000077500000000000000000000000001403641720100235565ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/__init__.py000066400000000000000000000000001403641720100256550ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/test_generator.py000066400000000000000000000046621403641720100271650ustar00rootroot00000000000000import os from ....utils.common import redirected_stdio from bloom.generators.debian.generator import em from bloom.generators.debian.generator import get_changelogs from bloom.generators.debian.generator import format_description from catkin_pkg.packages import find_packages test_data_dir = os.path.join(os.path.dirname(__file__), 'test_generator_data') def test_get_changelogs(): with redirected_stdio(): packages = dict([(pkg.name, pkg) for path, pkg in find_packages(test_data_dir).items()]) assert 'bad_changelog_pkg' in packages get_changelogs(packages['bad_changelog_pkg']) def test_unicode_templating(): with redirected_stdio(): packages = dict([(pkg.name, pkg) for path, pkg in find_packages(test_data_dir).items()]) assert 'bad_changelog_pkg' in packages chlogs = get_changelogs(packages['bad_changelog_pkg']) template = "@(changelog)" em.expand(template, {'changelog': chlogs[0][2]}) def test_format_description(): assert '' == format_description('') assert '.' == format_description('.') assert 'Word.' == format_description('Word.') assert 'Word' == format_description('Word') assert '.' == format_description(' .') assert '.' == format_description(' . ') assert 'Word.\n Other words.' == format_description('Word. Other words.') assert 'The first sentence, or synopsis.\n The second sentence. Part of the long description, but all in a single paragraph.' == format_description('The first sentence, or synopsis. The second sentence. Part of the long description, but all in a single paragraph.') assert '..' == format_description('..') assert 'The my_package package' == format_description('The my_package package') assert 'First sentence with a version nr: 2.4.5, some other text.\n And then some other text.' == format_description('First sentence with a version nr: 2.4.5, some other text. And then some other text.') assert 'More punctuation! This will split here.\n And the rest.' == format_description('More punctuation! This will split here. And the rest.') assert 'v1.2.3 with v5.3.7 and ! Split after this.\n Long description here.' == format_description('v1.2.3 with v5.3.7 and ! Split after this. Long description here.\n\n') # no whitespace between

's, no split assert 'some embedded html markup.the other sentence.' == format_description('

some embedded

\n

html markup.

the other sentence.

') bloom-0.10.7/test/unit_tests/test_generators/test_debian/test_generator_data/000077500000000000000000000000001403641720100275745ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/test_generator_data/bad_changelog_pkg/000077500000000000000000000000001403641720100331725ustar00rootroot00000000000000CHANGELOG.rst000066400000000000000000000003241403641720100351330ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/test_generator_data/bad_changelog_pkg^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Changelog for package bad_changelog_pkg ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 0.5.77 (2013-10-09) ------------------- * Contributors: Sömeöne with UTF-8 in their name package.xml000066400000000000000000000004511403641720100352300ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_generators/test_debian/test_generator_data/bad_changelog_pkg bad_changelog_pkg 0.0.0 The bad_changelog_pkg package nobody TODO catkin bloom-0.10.7/test/unit_tests/test_packages.py000066400000000000000000000010731403641720100212550ustar00rootroot00000000000000import os from ..utils.common import AssertRaisesContext from ..utils.common import in_temporary_directory from ..utils.common import redirected_stdio from ..utils.common import user from bloom.packages import get_package_data test_data_dir = os.path.join(os.path.dirname(__file__), 'test_packages_data') @in_temporary_directory def test_get_package_data_fails_on_uppercase(): user('git init .') with AssertRaisesContext(SystemExit, "Invalid package names, aborting."): with redirected_stdio(): get_package_data(directory=test_data_dir) bloom-0.10.7/test/unit_tests/test_packages_data/000077500000000000000000000000001403641720100216735ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_packages_data/bad_NAME/000077500000000000000000000000001403641720100232215ustar00rootroot00000000000000bloom-0.10.7/test/unit_tests/test_packages_data/bad_NAME/CMakeLists.txt000066400000000000000000000073561403641720100257740ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(bad_NAME) ## Find catkin macros and libraries ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) ## is used, also find other catkin packages find_package(catkin REQUIRED) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) ## Uncomment this if the package has a setup.py. This macro ensures ## modules and global scripts declared therein get installed ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html # catkin_python_setup() ####################################### ## Declare ROS messages and services ## ####################################### ## Generate messages in the 'msg' folder # add_message_files( # FILES # Message1.msg # Message2.msg # ) ## Generate services in the 'srv' folder # add_service_files( # FILES # Service1.srv # Service2.srv # ) ## Generate added messages and services with any dependencies listed here # generate_messages( # DEPENDENCIES # std_msgs # Or other packages containing msgs # ) ################################### ## catkin specific configuration ## ################################### ## The catkin_package macro generates cmake config files for your package ## Declare things to be passed to dependent projects ## INCLUDE_DIRS: uncomment this if you package contains header files ## LIBRARIES: libraries you create in this project that dependent projects also need ## CATKIN_DEPENDS: catkin_packages dependent projects also need ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( # INCLUDE_DIRS include # LIBRARIES bad_NAME # CATKIN_DEPENDS other_catkin_pkg # DEPENDS system_lib ) ########### ## Build ## ########### ## Specify additional locations of header files ## Your package locations should be listed before other locations # include_directories(include) ## Declare a cpp library # add_library(bad_NAME # src/${PROJECT_NAME}/bad_NAME.cpp # ) ## Declare a cpp executable # add_executable(bad_NAME_node src/bad_NAME_node.cpp) ## Add cmake target dependencies of the executable/library ## as an example, message headers may need to be generated before nodes # add_dependencies(bad_NAME_node bad_NAME_generate_messages_cpp) ## Specify libraries to link a library or executable target against # target_link_libraries(bad_NAME_node # ${catkin_LIBRARIES} # ) ############# ## Install ## ############# # all install targets should use catkin DESTINATION variables # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html ## Mark executable scripts (Python etc.) for installation ## in contrast to setup.py, you can choose the destination # install(PROGRAMS # scripts/my_python_script # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark executables and/or libraries for installation # install(TARGETS bad_NAME bad_NAME_node # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark cpp header files for installation # install(DIRECTORY include/${PROJECT_NAME}/ # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} # FILES_MATCHING PATTERN "*.h" # PATTERN ".svn" EXCLUDE # ) ## Mark other files for installation (e.g. launch and bag files, etc.) # install(FILES # # myfile1 # # myfile2 # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # ) ############# ## Testing ## ############# ## Add gtest based cpp test target and link libraries # catkin_add_gtest(${PROJECT_NAME}-test test/test_bad_NAME.cpp) # if(TARGET ${PROJECT_NAME}-test) # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) # endif() ## Add folders to be run by python nosetests # catkin_add_nosetests(test) bloom-0.10.7/test/unit_tests/test_packages_data/bad_NAME/package.xml000066400000000000000000000037051403641720100253430ustar00rootroot00000000000000 bad_NAME 0.0.0 The bad_NAME package william TODO catkin bloom-0.10.7/test/utils/000077500000000000000000000000001403641720100150245ustar00rootroot00000000000000bloom-0.10.7/test/utils/__init__.py000066400000000000000000000000001403641720100171230ustar00rootroot00000000000000bloom-0.10.7/test/utils/common.py000066400000000000000000000331171403641720100166730ustar00rootroot00000000000000""" Common tools for running tests """ from __future__ import print_function import functools import os import re import shlex import shutil import sys import tempfile try: # Python2 from StringIO import StringIO except ImportError: # Python3 from io import StringIO from subprocess import Popen, PIPE, CalledProcessError import yaml def assert_raises(exception_classes, callable_obj=None, *args, **kwargs): context = AssertRaisesContext(exception_classes) if callable_obj is None: return context with context: callable_obj(*args, **kwargs) def assert_raises_regex(exception_classes, expected_regex, callable_obj=None, *args, **kwargs): context = AssertRaisesContext(exception_classes, expected_regex) if callable_obj is None: return context with context: callable_obj(*args, **kwargs) class AssertRaisesContext(object): def __init__(self, expected, expected_regex=None): self.expected = expected self.expected_regex = expected_regex def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): if self.expected is None: if exc_type is None: return True else: raise if exc_type is None: try: exc_name = self.expected.__name__ except AttributeError: exc_name = str(self.expected) raise AssertionError("{0} not raised".format(exc_name)) if not issubclass(exc_type, self.expected): raise if self.expected_regex is None: return True expected_regex = self.expected_regex expected_regex = re.compile(expected_regex) if not expected_regex.search(str(exc_value)): raise AssertionError("'{0}' does not match '{1}'".format(expected_regex.pattern, str(exc_value))) return True class bloom_answer(object): ASSERT_NO_QUESTION = -1 def __init__(self, answer, util_module=None): self.answer = answer if util_module is None: import bloom.util as util_module import bloom.commands.git.config as config_module self.util_module = util_module self.config_module = config_module def __call__(self, msg=None): if msg is not None: print(msg) assert self.answer != self.ASSERT_NO_QUESTION, \ "bloom asked a question, and it should not have" if isinstance(self.answer, str): print(self.answer) return self.answer elif isinstance(self.answer, list): if self.answer: print(self.answer) return self.answer.pop(0) else: print('Nada') return '' else: assert False, "Invalid answers given to bloom_answer" def __enter__(self): self.orig_util_module_input = self.util_module.safe_input self.orig_config_module_input = self.config_module.safe_input self.util_module.safe_input = self self.config_module.safe_input = self def __exit__(self, exc_type, exc_value, traceback): self.util_module.safe_input = self.orig_util_module_input self.config_module.safe_input = self.orig_config_module_input class change_directory(object): def __init__(self, directory=''): self.directory = directory self.original_cwd = None def __enter__(self): self.original_cwd = os.getcwd() os.chdir(self.directory) return self.directory def __exit__(self, exc_type, exc_value, traceback): if self.original_cwd and os.path.exists(self.original_cwd): os.chdir(self.original_cwd) class change_environ(object): def __init__(self, env=None): self.original_env = os.environ self.new_env = dict(env) if env is not None else dict(os.environ) def __enter__(self): self.original_env = os.environ os.environ = dict(self.new_env) def __exit__(self, exc_type, exc_value, traceback): os.environ = self.original_env def in_temporary_directory(f): @functools.wraps(f) def decorated(*args, **kwds): with temporary_directory() as directory: from inspect import getargspec # If it takes directory of kwargs and kwds does already have # directory, inject it if 'directory' not in kwds and 'directory' in getargspec(f)[0]: kwds['directory'] = directory return f(*args, **kwds) decorated.__name__ = f.__name__ return decorated class redirected_stdio(object): def __enter__(self): self.original_stdout = sys.stdout self.original_stderr = sys.stderr sys.stdout = out = StringIO() sys.stderr = err = StringIO() return out, err def __exit__(self, exc_type, exc_value, traceback): sys.stdout = self.original_stdout sys.stderr = self.original_stderr class temporary_directory(object): def __init__(self, prefix=''): self.prefix = prefix def __enter__(self): self.original_cwd = os.getcwd() self.temp_path = tempfile.mkdtemp(prefix=self.prefix) os.chdir(self.temp_path) return self.temp_path def __exit__(self, exc_type, exc_value, traceback): if self.temp_path and os.path.exists(self.temp_path): shutil.rmtree(self.temp_path) if self.original_cwd and os.path.exists(self.original_cwd): os.chdir(self.original_cwd) def user_bloom(cmd, args=None, directory=None, auto_assert=True, return_io=True, silent=False, env=None): """Runs the given bloom cmd ('git-bloom-{cmd}') with the given args""" assert type(cmd) == str, \ "user_bloom cmd takes str only, got " + str(type(cmd)) if args is None: args = cmd.split()[1:] cmd = cmd.split()[0] assert type(args) in [list, tuple, str], \ "user_bloom args takes [list, tuple, str] only, got " + \ str(type(args)) from pkg_resources import load_entry_point from bloom import __version__ as ver if not cmd.startswith('git-bloom-'): cmd = 'git-bloom-' + cmd if type(args) != list: if type(args) == str: args = args.split() args = list(args) with change_directory(directory if directory is not None else os.getcwd()): with redirected_stdio() as (out, err): func = load_entry_point('bloom==' + ver, 'console_scripts', cmd) try: with change_environ(env): ret = func(args) or 0 except SystemExit as e: ret = e.code if ret != 0 and auto_assert: raise if not silent: print("Command '{0}' returned '{1}':".format(cmd, ret)) print(out.getvalue(), file=sys.stdout, end='') print(err.getvalue(), file=sys.stderr, end='') if return_io: return ret, out.getvalue(), err.getvalue() return ret def user_cd(cmd, **kwargs): """ Used in system tests to emulate a user changing directories Used in place of user('cd ') """ if type(cmd) is str: assert cmd != '', "no arguments passed to cd, not allowed" cmd = cmd.split() new_directory = cmd[0] assert os.path.exists(new_directory), \ "user tried to cd to '" + new_directory + "' which does not exist" os.chdir(new_directory) return 0 def user_echo(cmd, **kwargs): """ Used to emulate the user echoing something even to a file with >> """ assert type(cmd) is str, "user echo only takes str for the cmd argument" cmd = shlex.split(cmd) if len(cmd) == 1: print(cmd[0]) elif len(cmd) == 3 and cmd[1] in ['>>', '>']: # echo into somefile assert not os.path.isdir(cmd[2]), \ "user tried to echo into a directory: '" + cmd[2] + "'" if cmd[1] == '>>': mode = 'a' else: mode = 'w+' with open(cmd[2], mode) as f: f.write(cmd[0]) return 0 def user_mkdir(cmd, **kwargs): """ Used in system tests to emulte a user creating a directory """ if type(cmd) is str: assert cmd != '', "no arguments passed to mkdir, not allowed" cmd = cmd.split() if len(cmd) == 2: assert '-p' in cmd, "two args to mkdir, neither is '-p', not allowed" cmd.remove('-p') mkdir_cmd = os.makedirs else: mkdir_cmd = os.mkdir new_dir = cmd[0] assert not os.path.exists(new_dir), \ "directory '" + new_dir + "' already exists" mkdir_cmd(new_dir) return 0 def user_touch(cmd, **kwargs): """ Used to emulat a user touching a file """ assert not os.path.exists(cmd), \ "user tried to touch a file '" + cmd + "' but it exists" if os.path.exists(cmd): os.utime(cmd, None) else: open(cmd, 'w').close() _special_user_commands = { 'cd': user_cd, 'echo': user_echo, 'git-bloom-': user_bloom, 'mkdir': user_mkdir, 'touch': user_touch } def user(cmd, directory=None, auto_assert=True, return_io=False, bash_only=False, silent=True, env=None): """Used in system tests to emulate a user action""" if type(cmd) in [list, tuple]: cmd = ' '.join(cmd) if not bash_only: # Handle special cases for case in _special_user_commands: if cmd.startswith(case): cmd = ''.join(cmd.split(case)[1:]).strip() return _special_user_commands[case]( cmd, directory=directory, auto_assert=auto_assert, return_io=return_io, silent=silent, env=env ) ret = -1 try: p = Popen(cmd, shell=True, cwd=directory, env=env, stdout=PIPE, stderr=PIPE) out, err = p.communicate() if out is not None and not isinstance(out, str): out = out.decode('utf-8') if err is not None and not isinstance(err, str): err = err.decode('utf-8') ret = p.returncode except CalledProcessError as err: ret = err.returncode if not silent: print(out, file=sys.stdout, end='') print(err, file=sys.stderr, end='') if auto_assert: assert ret == 0, \ "user command '" + cmd + "' returned " + str(p.returncode) if return_io: return ret, out, err return ret def set_up_fake_rosdep(staging_dir, distros={}, rules={}): """ Used to create a 'fake' rosdep cache from a locally generated index. Example invocation: .. code-block:: python env = dict(os.environ) env.update(set_up_fake_rosdep( '/tmp/fake_rosdep', { 'melodic': { 'ubuntu': ['bionic'] } }, { 'some_rosdep_key': { 'ubuntu': ['some-package-name'] } })) :param staging_dir: Scratch directory in which to make infrastructure files. :param distros: Mapping of ROS distributions to populate the index with. :param rules: rosdep rules to populate the rosdep database with. :returns: Environment variables which cause Bloom will use the fake cache. """ # Construct bare environment rosdistro_dir = os.path.join(staging_dir, 'rosdistro') rosdistro_index_path = os.path.join(rosdistro_dir, 'index.yaml') sources_list_dir = os.path.join(staging_dir, 'sources.list.d') ros_home_dir = os.path.join(staging_dir, 'ros_home') os.makedirs(rosdistro_dir) os.makedirs(sources_list_dir) os.makedirs(ros_home_dir) env = { 'BLOOM_SKIP_ROSDEP_UPDATE': '1', 'ROSDEP_SOURCE_PATH': os.path.realpath(sources_list_dir), 'ROSDISTRO_INDEX_URL': 'file://' + os.path.realpath(rosdistro_index_path), 'ROS_HOME': os.path.realpath(ros_home_dir) } # Create the specified distributions for distro, platforms in distros.items(): distro_dir = os.path.join(rosdistro_dir, distro) distro_yaml_path = os.path.join(distro_dir, 'distribution.yaml') os.makedirs(distro_dir) distro_yaml_data = { 'release_platforms': platforms, 'repositories': dict({}), 'type': 'distribution', 'version': 2 } with open(distro_yaml_path, 'w') as f: f.write(yaml.dump(distro_yaml_data)) # Index the specified distributions rosdistro_index_data = { 'distributions': { distro: { 'distribution': [os.path.join(distro, 'distribution.yaml')], 'distribution_cache': 'DOES-NOT-EXIST', 'distribution_status': 'active', 'distribution_type': 'ros1', 'python_version': 2 } for distro in distros.keys() }, 'type': 'index', 'version': 4 } with open(rosdistro_index_path, 'w') as f: f.write(yaml.dump(rosdistro_index_data)) # Create the rosdep database rosdep_db_dir = os.path.join(rosdistro_dir, 'rosdep') os.makedirs(rosdep_db_dir) rosdep_db_path = os.path.join(rosdep_db_dir, 'rosdep.yaml') with open(rosdep_db_path, 'w') as f: f.write(yaml.dump(rules)) # Create the rosdep sources list rosdistro_source_path = os.path.join(sources_list_dir, '50-rosdep.list') with open(rosdistro_source_path, 'w') as f: f.write('yaml file://' + os.path.realpath(rosdep_db_path)) # Perform the initial rosdep update setup_env = dict(os.environ) setup_env.update(env) user('rosdep update', env=setup_env) return env bloom-0.10.7/test/utils/package_version.py000066400000000000000000000124201403641720100205350ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import os import re from ..utils.common import change_directory from ..utils.common import user def bump_version(version, bump='patch'): """ Increases version number. :param version: str, must be in version format "int.int.int" :param bump: str, one of 'patch, minor, major' :returns: version with the given part increased, and all inferior parts reset to 0 """ # split the version number match = re.match('^(\d+)\.(\d+)\.(\d+)$', version) if match is None: raise ValueError( 'Invalid version string, must be int.int.int: "%s"' % version ) new_version = match.groups() new_version = [int(x) for x in new_version] # find the desired index idx = dict(major=0, minor=1, patch=2)[bump] # increment the desired part new_version[idx] += 1 # reset all parts behind the bumped part new_version = new_version[:idx + 1] + [0 for x in new_version[idx + 1:]] return '%d.%d.%d' % tuple(new_version) def _replace_version(package_str, new_version): """ replaces the version tag in contents if there is only one instance :param package_str: str contents of package.xml :param new_version: str version number :returns: str new package.xml :raises RuntimeError: """ # try to replace contens new_package_str, number_of_subs = re.subn( ']*)>[^<>]*
', '>%s
' % new_version, package_str ) if number_of_subs != 1: raise RuntimeError( 'Illegal number of version tags: %s' % (number_of_subs) ) return new_package_str def _check_for_version_comment(package_str, new_version): """ checks if a comment is present behind the version tag and return it :param package_str: str contents of package.xml :param version: str version number :returns: str comment if available, else None """ version_tag = '>%s
' % new_version pattern = '%s[ \t]*%s *(.+) *%s' % ( re.escape(version_tag), re.escape('') ) comment = re.search(pattern, package_str) if comment: comment = comment.group(1) return comment def update_versions(paths, new_version): """ bulk replace of version: searches for package.xml files directly in given folders and replaces version tag within. :param paths: list of string, folder names :param new_version: version string "int.int.int" :raises RuntimeError: if any one package.xml cannot be updated """ files = {} for path in paths: package_path = os.path.join(path, 'package.xml') with open(package_path, 'r') as f: package_str = f.read() try: new_package_str = _replace_version(package_str, new_version) comment = _check_for_version_comment(new_package_str, new_version) if comment: print('''\ NOTE: The package manifest "%s" contains a comment besides the version tag: %s\ ''' % (path, comment)) except RuntimeError as rue: raise RuntimeError( 'Could not bump version number in file ' '%s: %s' % (package_path, str(rue)) ) files[package_path] = new_package_str # if all replacements successful, write back modified package.xml for package_path, new_package_str in files.items(): with open(package_path, 'w') as f: f.write(new_package_str) def change_upstream_version(upstream_dir, version): update_versions([upstream_dir], version) with change_directory(upstream_dir): user('git commit -am "Version {0}" --allow-empty'.format(version)) user('git tag ' + version) bloom-0.10.7/test/utils/script_runner.py000066400000000000000000000004611403641720100202740ustar00rootroot00000000000000import os from subprocess import Popen def popen_bloom_script(cmd, **kwargs): this_location = os.path.abspath(os.path.dirname(__file__)) bin_location = os.path.join(this_location, '..', 'bin') cmd = "%s%s%s" % (bin_location, os.path.sep, cmd) proc = Popen(cmd, **kwargs) return proc