diff options
author | Markus Lehtonen <markus.lehtonen@linux.intel.com> | 2014-08-22 16:17:49 +0300 |
---|---|---|
committer | Markus Lehtonen <markus.lehtonen@linux.intel.com> | 2014-12-19 13:32:27 +0200 |
commit | 4251243b995152289cd8cfd1444be6cea75148a2 (patch) | |
tree | bfebc93a5ac5d13cdfa03de8e08027ebb7f01c18 | |
parent | e021ecaa4ff2aeaff4d103d391811fb6c88f68c7 (diff) | |
download | git-buildpackage-4251243b995152289cd8cfd1444be6cea75148a2.tar.gz git-buildpackage-4251243b995152289cd8cfd1444be6cea75148a2.tar.bz2 git-buildpackage-4251243b995152289cd8cfd1444be6cea75148a2.zip |
Introduce import-bb tool
This is the first tool in an effort of enabling gbp in the BitBake build
environment. Gbp-import-bb is a tool for importing packages from a
BitBake-based "combined" distro repository into individual per-package
Git repositories.
Change-Id: I86e27d8a1817eae30c3f828cea70b06162543000
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
-rw-r--r-- | debian/control | 13 | ||||
-rw-r--r-- | debian/git-buildpackage-bb.install | 2 | ||||
-rw-r--r-- | gbp/bb/__init__.py | 66 | ||||
-rw-r--r-- | gbp/config.py | 14 | ||||
-rwxr-xr-x | gbp/scripts/import_bb.py | 457 | ||||
-rw-r--r-- | packaging/git-buildpackage.spec | 21 |
6 files changed, 573 insertions, 0 deletions
diff --git a/debian/control b/debian/control index 773f339a..db7e05fb 100644 --- a/debian/control +++ b/debian/control @@ -74,3 +74,16 @@ Description: Suite to help with RPM packages in Git repositories * git-buildpackage-rpm: build a package out of a git repository, check for local modifications and tag appropriately * gbp-pq-rpm: manage separate development and packaging branches + +Package: git-buildpackage-bb +Architecture: all +Depends: ${python:Depends}, + ${misc:Depends}, + git-buildpackage-common (= ${binary:Version}), + git-buildpackage-rpm (= ${binary:Version}), +Recommends: bitbake +Description: Suite to help with BitBake builds from Git repositories + This package contains the following tools: + * gbp import-bb: import sources from distribution repository + * gbp buildpackage-bb: build a package out of a Git repository + * gbp pq-bb: manage separate development and packaging branches diff --git a/debian/git-buildpackage-bb.install b/debian/git-buildpackage-bb.install new file mode 100644 index 00000000..39b39afa --- /dev/null +++ b/debian/git-buildpackage-bb.install @@ -0,0 +1,2 @@ +usr/lib/python2.?/dist-packages/gbp/bb/ +usr/lib/python2.?/dist-packages/gbp/scripts/*bb*.py* diff --git a/gbp/bb/__init__.py b/gbp/bb/__init__.py new file mode 100644 index 00000000..77af1f53 --- /dev/null +++ b/gbp/bb/__init__.py @@ -0,0 +1,66 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2014 Intel Corporation <markus.lehtonen@linux.intel.com> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Bitbake helper functionality""" + +import os +import subprocess +import sys + +import gbp.log + +bb = None + +# pylint: disable=bad-continuation + + +def import_bb(): + """Import bitbake lib""" + bb_bin = subprocess.Popen(['which', 'bitbake'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate()[0] + if bb_bin: + bb_lib_path = os.path.dirname(bb_bin) + '/../lib' + sys.path.insert(0, bb_lib_path) + try: + return __import__('bb') + except ImportError: + print "ERROR: Unable to find bitbake/lib, try initializing build " \ + "environment with the 'oe-init-build-env' script\n" + # Return None instead of raising (ImportError) so that building of + # this package succeeds in Debian. Otherwise dpkg-buildpackage fails + # because of an import error in epydoc. + return None + +def init_tinfoil(config_only=False, tracking=False): + """Initialize the Bitbake tinfoil module""" + import bb.tinfoil + try: + tinfoil = bb.tinfoil.Tinfoil(tracking=tracking) + except bb.BBHandledException: + raise GbpError("Failed to initialize tinfoil") + tinfoil.prepare(config_only=config_only) + return tinfoil + + +def pkg_version(data): + """Get package version as a dict""" + return {'upstreamversion': data.getVar('PV', True), + 'release': data.getVar('PR', True), + 'version': data.getVar('PV', True) + '-' + data.getVar('PR', True)} + + +# Initialize module +bb = import_bb() diff --git a/gbp/config.py b/gbp/config.py index dd0c6313..fba9985f 100644 --- a/gbp/config.py +++ b/gbp/config.py @@ -702,4 +702,18 @@ class GbpOptionParserRpm(GbpOptionParser): "Meta tags for the bts commands, default is '%(meta-bts)s'", }) +class GbpOptionParserBB(GbpOptionParserRpm): + """Commandline and config file option parser for the -bb commands""" + defaults = dict(GbpOptionParserRpm.defaults) + defaults.update( { + 'meta-dir' : '', + } ) + + help = dict(GbpOptionParserRpm.help) + help.update( { + 'meta-dir': + "Subdir where bitbake meta files are stored, default " + "is '%(meta-dir)s'", + } ) + # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/gbp/scripts/import_bb.py b/gbp/scripts/import_bb.py new file mode 100755 index 00000000..446ead30 --- /dev/null +++ b/gbp/scripts/import_bb.py @@ -0,0 +1,457 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2014 Intel Corporation <markus.lehtonen@linux.intel.com> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""Import an RPM package in Bitbake format""" + +import ConfigParser +import sys +import os +import shutil + +import gbp.tmpfile as tempfile +import gbp.command_wrappers as gbpc +import gbp.log +from gbp.rpm import RpmUpstreamSource +from gbp.rpm.policy import RpmPkgPolicy +from gbp.rpm.git import RpmGitRepository, GitRepositoryError +from gbp.config import (GbpOptionParserBB, GbpOptionGroup, + no_upstream_branch_msg) +from gbp.errors import GbpError +from gbp.pkg import parse_archive_filename +from gbp.scripts.import_srpm import move_tag_stamp, force_to_branch_head +from gbp.bb import bb, init_tinfoil, pkg_version + +# pylint: disable=bad-continuation + +NO_PACKAGING_BRANCH_MSG = """ +Repository does not have branch '%s' for meta/packaging files. +You need to reate it or use --packaging-branch to specify it. +""" + +class SkipImport(Exception): + """Nothing imported""" + pass + +def set_bare_repo_options(options): + """Modify options for import into a bare repository""" + if options.pristine_tar: + gbp.log.info("Bare repository: setting %s option '--no-pristine-tar'") + options.pristine_tar = False + + +def build_parser(name): + """Create command line parser""" + try: + parser = GbpOptionParserBB(command=os.path.basename(name), + prefix='', + usage='%prog [options] /path/to/package' + '.src.rpm') + except ConfigParser.ParsingError, err: + gbp.log.err(err) + return None + + import_group = GbpOptionGroup(parser, "import options", + "pristine-tar and filtering") + tag_group = GbpOptionGroup(parser, "tag options", + "options related to git tag creation") + branch_group = GbpOptionGroup(parser, "version and branch naming options", + "version number and branch layout options") + + for group in [import_group, branch_group, tag_group ]: + parser.add_option_group(group) + + parser.add_option("-v", "--verbose", action="store_true", dest="verbose", + default=False, help="verbose command execution") + parser.add_config_file_option(option_name="color", dest="color", + type='tristate') + parser.add_config_file_option(option_name="color-scheme", + dest="color_scheme") + parser.add_config_file_option(option_name="tmp-dir", dest="tmp_dir") + parser.add_config_file_option(option_name="vendor", action="store", + dest="vendor") + branch_group.add_config_file_option(option_name="packaging-branch", + dest="packaging_branch") + branch_group.add_config_file_option(option_name="upstream-branch", + dest="upstream_branch") + branch_group.add_option("--upstream-vcs-tag", dest="vcs_tag", + help="Upstream VCS tag on top of which to import " + "the orig sources") + branch_group.add_boolean_config_file_option( + option_name="create-missing-branches", + dest="create_missing_branches") + + tag_group.add_boolean_config_file_option(option_name="sign-tags", + dest="sign_tags") + tag_group.add_config_file_option(option_name="keyid", + dest="keyid") + tag_group.add_config_file_option(option_name="packaging-tag", + dest="packaging_tag") + tag_group.add_config_file_option(option_name="upstream-tag", + dest="upstream_tag") + + import_group.add_config_file_option(option_name="filter", + dest="filters", action="append") + import_group.add_boolean_config_file_option(option_name="pristine-tar", + dest="pristine_tar") + import_group.add_option("--allow-same-version", action="store_true", + dest="allow_same_version", default=False, + help="allow to import already imported version") + import_group.add_config_file_option(option_name="meta-dir", + dest="meta_dir") + return parser + +def parse_args(argv): + """Parse commandline arguments""" + parser = build_parser(argv[0]) + if not parser: + return None, None + + (options, args) = parser.parse_args(argv[1:]) + gbp.log.setup(options.color, options.verbose, options.color_scheme) + return options, args + + +def guess_bb(pkg_dir, tinfoil): + """Guess a bb from a directory""" + abspath = os.path.abspath(pkg_dir) + layer_dirs = tinfoil.config_data.getVar('BBLAYERS').split() + gbp.log.debug("Checking if %s is in %s" % (abspath, layer_dirs)) + layer_dir = '' + for path in layer_dirs: + if abspath.startswith(path): + layer_dir = path + if not layer_dir: + raise GbpError("%s not under configured layers" % abspath) + + bb_files = [path for path in tinfoil.cooker_data.pkg_fn + if os.path.dirname(path) == abspath] + if len(bb_files): + bb_file = bb_files[-1] + gbp.log.debug("Found %d recipes in %s, choosing %s" % + (len(bb_files), pkg_dir, os.path.basename(bb_file))) + else: + raise GbpError("No recipes found in %s" % pkg_dir) + return bb_file + +def guess_pkg(pkg, tinfoil): + """Determine the package to import""" + if pkg in tinfoil.cooker_data.pkg_pn: + pkg_bb = tinfoil.cooker_data.pkg_pn[pkg][0] + elif not os.path.isdir(pkg): + abspath = os.path.abspath(pkg) + if abspath in tinfoil.cooker_data.pkg_fn: + pkg_bb = abspath + else: + raise GbpError("Package %s not found in any configured layer" % pkg) + elif os.path.exists(pkg): + pkg_bb = guess_bb(pkg, tinfoil) + else: + raise GbpError("Unable to find %s" % pkg) + return pkg_bb + +def init_repo(path): + """Check and initialize Git repository""" + try: + repo = RpmGitRepository(path) + clean, out = repo.is_clean() + if not clean and not repo.is_empty(): + gbp.log.err("Repository has uncommitted changes, commit " + "these first:") + gbp.log.err(out) + raise GbpError + except GitRepositoryError: + gbp.log.info("No git repository found, creating one in %s" % path) + repo = RpmGitRepository.create(path) + return repo + +def recursive_copy(src, dst): + """Recursive copy, overwriting files and preserving symlinks""" + # Remove existing destinations, if needed + if os.path.isfile(dst) or os.path.islink(dst): + os.unlink(dst) + elif (os.path.isfile(src) or os.path.islink(src)) and os.path.isdir(dst): + # Remove dst dir if src is a file + shutil.rmtree(dst) + + try: + if os.path.islink(src): + os.symlink(os.readlink(src), dst) + elif os.path.isdir(src): + if not os.path.exists(dst): + os.makedirs(dst) + for fname in os.listdir(src): + recursive_copy(os.path.join(src, fname), + os.path.join(dst, fname)) + else: + shutil.copy2(src, dst) + except (IOError, OSError) as err: + raise GbpError("Error while copying '%s' to '%s': %s" % (src, dst, err)) + +def guess_upstream_source(pkg_data, remotes): + """Guess the primary upstream source archive.""" + orig = None + name = pkg_data.getVar('PN', True) + + for fetch_data in remotes: + if fetch_data.type == 'git': + orig = fetch_data + else: + path = fetch_data.localpath + fname = os.path.basename(path) + fn_base, archive_fmt, _ = parse_archive_filename(fname) + if fn_base.startswith(name) and archive_fmt: + # Take an archive that starts with pkg name + orig = fetch_data + # otherwise we take the first archive + elif not orig and archive_fmt: + orig = fetch_data + # else don't accept + return orig + +def bb_get_files(pkg_data, tgt_dir, whole_dir=False, download=True): + """Get (local) packaging files""" + uris = (pkg_data.getVar('SRC_URI', True) or "").split() + try: + fetch = bb.fetch2.Fetch(uris, pkg_data) + if download: + gbp.log.info("Fetching sources...") + fetch.download() + except bb.fetch2.BBFetchException as err: + raise GbpError("Failed to fetch packaging files: %s" % err) + + # Copy local files to target directory + bb_dir = os.path.dirname(pkg_data.getVar('FILE', True)) + remote = [] + local = [path for path in pkg_data.getVar('BBINCLUDED', True).split() if + path.startswith(bb_dir) and os.path.exists(path)] + for url in fetch.urls: + path = fetch.localpath(url) + if path.startswith(bb_dir): + if not whole_dir: + gbp.log.debug("Found local meta file '%s'" % path) + local.append(path) + else: + gbp.log.debug("Found remote file '%s'" % path) + remote.append(fetch.ud[url]) + + if whole_dir: + # Simply copy whole meta dir, if requested + recursive_copy(bb_dir, tgt_dir) + else: + for path in local: + relpath = os.path.relpath(path, bb_dir) + subdir = os.path.join(tgt_dir, os.path.dirname(relpath)) + if not os.path.exists(subdir): + os.makedirs(subdir) + shutil.copy2(path, os.path.join(tgt_dir, relpath)) + + return remote + +def import_upstream_archive(repo, pkg_data, fetch_data, dirs, options): + """Import upstream sources from archive""" + # Unpack orig source archive + path = fetch_data.localpath + sources = RpmUpstreamSource(path) + sources = sources.unpack(dirs['origsrc'], options.filters) + + tag_str_fields = dict(pkg_version(pkg_data), vendor=options.vendor.lower()) + tag = repo.version_to_tag(options.upstream_tag, tag_str_fields) + if not repo.has_tag(tag): + gbp.log.info("Tag %s not found, importing upstream sources" % tag) + branch = options.upstream_branch + + msg = "Upstream version %s" % tag_str_fields['upstreamversion'] + if options.vcs_tag: + parents = [repo.rev_parse("%s^{}" % options.vcs_tag)] + else: + parents = None + commit = repo.commit_dir(sources.unpacked, "Imported %s" % msg, + branch, other_parents=parents, + create_missing_branch=options.create_missing_branches) + repo.create_tag(name=tag, msg=msg, commit=commit, + sign=options.sign_tags, keyid=options.keyid) + + if options.pristine_tar: + archive_fmt = parse_archive_filename(path)[1] + if archive_fmt == 'tar': + repo.pristine_tar.commit(path, 'refs/heads/%s' % branch) + else: + gbp.log.warn('Ignoring pristine-tar, %s archives ' + 'not supported' % archive_fmt) + return repo.rev_parse('%s^0' % tag) + +def import_upstream_git(repo, fetch_data, options): + """Import upstream sources from Git""" + # Fetch from local cached repo + for branch in fetch_data.branches.values(): + repo.fetch(repo=fetch_data.localpath, refspec=branch) + + commit = fetch_data.revision + repo.update_ref('refs/heads/' + options.upstream_branch, commit) + return commit + +def import_upstream_sources(repo, pkg_data, remotes, dirs, options): + """Import upstream sources to Git""" + fetch_data = guess_upstream_source(pkg_data, remotes) + if fetch_data: + gbp.log.debug("Using upstream source '%s'" % fetch_data.url) + else: + gbp.log.info("No orig source archive imported") + return + + if not repo.has_branch(options.upstream_branch): + if options.create_missing_branches: + gbp.log.info("Will create missing branch '%s'" % + options.upstream_branch) + else: + gbp.log.err(no_upstream_branch_msg % options.upstream_branch + "\n" + "Also check the --create-missing-branches option.") + raise GbpError + + if fetch_data.type == 'git': + return import_upstream_git(repo, fetch_data, options) + else: + return import_upstream_archive(repo, pkg_data, fetch_data, dirs, + options) + + +def main(argv): + """Main function of the gbp import-bb script""" + dirs = dict(top=os.path.abspath(os.curdir)) + ret = 0 + skipped = False + + if not bb: + return 1 + + options, args = parse_args(argv) + + if len(args) == 0 or len(args) > 2: + gbp.log.err("Need to give exactly one package to import. Try --help.") + return 1 + + try: + dirs['tmp_base'] = tempfile.mkdtemp(dir=options.tmp_dir, + prefix='import-bb') + tinfoil = init_tinfoil() + pkg_bb = guess_pkg(args[0], tinfoil) + dirs['src'] = os.path.abspath(os.path.dirname(pkg_bb)) + gbp.log.info("Importing '%s' from '%s'" % + (os.path.basename(pkg_bb), dirs['src'])) + + pkg_data = bb.cache.Cache.loadDataFull(pkg_bb, [], tinfoil.config_data) + + # Determine target repo dir + target_dir = '' + if len(args) == 2: + target_dir = args[1] + else: + if 'BUILDDIR' in os.environ: + target_dir = os.path.join(os.environ['BUILDDIR'], 'devel') + target_dir = os.path.join(target_dir, pkg_data.getVar('PN', True)) + + # Check the Git repository state + repo = init_repo(target_dir) + if repo.bare: + set_bare_repo_options(options) + if repo.is_empty(): + options.create_missing_branches = True + os.chdir(repo.path) + + # Create more tempdirs + dirs['origsrc'] = tempfile.mkdtemp(dir=dirs['tmp_base'], + prefix='origsrc_') + dirs['packaging_base'] = tempfile.mkdtemp(dir=dirs['tmp_base'], + prefix='packaging_') + dirs['packaging'] = os.path.join(dirs['packaging_base'], + options.meta_dir) + + # Copy (local) packaging files to tmp dir + remote_srcs = bb_get_files(pkg_data, dirs['packaging']) + + version_dict = pkg_version(pkg_data) + tag_str_fields = dict(version_dict, vendor=options.vendor.lower()) + ver_str = RpmPkgPolicy.compose_full_version(version_dict) + + # Check if the same version of the package is already imported + if repo.find_version(options.packaging_tag, tag_str_fields): + gbp.log.warn("Version %s already imported." % ver_str) + if options.allow_same_version: + gbp.log.info("Moving tag of version '%s' since import forced" % + ver_str) + move_tag_stamp(repo, options.packaging_tag, tag_str_fields) + else: + raise SkipImport + + # Import upstream sources + import_upstream_sources(repo, pkg_data, remote_srcs, dirs, options) + + # Import packaging files + gbp.log.info("Importing local meta/packaging files") + branch = options.packaging_branch + if not repo.has_branch(branch): + if options.create_missing_branches: + gbp.log.info("Will create missing branch '%s'" % branch) + else: + gbp.log.err(NO_PACKAGING_BRANCH_MSG % branch + "\n" + "Also check the --create-missing-branches " + "option.") + raise GbpError + + tag = repo.version_to_tag(options.packaging_tag, tag_str_fields) + msg = "%s release %s" % (options.vendor, ver_str) + + commit = repo.commit_dir(dirs['packaging_base'], + "Imported %s" % msg, + branch, + create_missing_branch=options.create_missing_branches) + + # Create packaging tag + repo.create_tag(name=tag, + msg=msg, + commit=commit, + sign=options.sign_tags, + keyid=options.keyid) + + force_to_branch_head(repo, options.packaging_branch) + + except KeyboardInterrupt: + ret = 1 + gbp.log.err("Interrupted. Aborting.") + except gbpc.CommandExecFailed: + ret = 1 + except GitRepositoryError as err: + gbp.log.err("Git command failed: %s" % err) + ret = 1 + except GbpError as err: + if len(err.__str__()): + gbp.log.err(err) + ret = 1 + except SkipImport: + skipped = True + finally: + os.chdir(dirs['top']) + gbpc.RemoveTree(dirs['tmp_base'])() + + if not ret and not skipped: + gbp.log.info("Version '%s' imported under '%s'" % + (ver_str, repo.path)) + return ret + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + +# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/packaging/git-buildpackage.spec b/packaging/git-buildpackage.spec index fdb4408d..8019ff8a 100644 --- a/packaging/git-buildpackage.spec +++ b/packaging/git-buildpackage.spec @@ -122,6 +122,20 @@ Set of tools from Debian that integrate the package build system with Git. This package contains the tools for building RPM packages. +%package bb +Summary: Build with BitBake from git +Group: Development/Tools/Building +Requires: %{name}-common = %{version}-%{release} +Requires: %{name}-rpm = %{version}-%{release} +%if 0%{?suse_version} || 0%{?tizen_version:1} +Recommends: bitbake +%endif + +%description bb +Set of tools from Debian that integrate the package build system with Git. +This package contains the tools for building with the BitBake tool. + + %if %{with docs} %package doc Summary: Documentation for the git-buildpackage suite @@ -264,6 +278,13 @@ done %endif +%files bb +%defattr(-,root,root,-) +%dir %{python_sitelib}/gbp/bb +%{python_sitelib}/gbp/scripts/*bb*.py* +%{python_sitelib}/gbp/bb/*py* + + %if %{with docs} %files doc %defattr(-,root,root,-) |