#!/usr/bin/python # rpmci_srpm_builder_main.py: # Implementation of rpm-vcs-mirror # # Licensed under the new-BSD license (http://www.opensource.org/licenses/bsd-license.php) # Copyright (C) 2010 Red Hat, Inc. # Written by Colin Walters import os import sys import time import shutil import optparse from ConfigParser import SafeConfigParser import logging import tempfile import subprocess import urllib import glib import gobject import gio from . import msgqueue from . import subtask from . import versioned_repos from . import artifact from . import spec from . import rpmutils from . import lame_vcs_abstraction class SRPMBuilder(object): def __init__(self, options, config): self.config = config self._options = options mirror_dir = config.get('VCS', 'mirror_dir') vcs_msgqueue_dir = config.get('VCS', 'msgqueue') self._vcs_msgqueue = msgqueue.MessageQueue(vcs_msgqueue_dir) srpm_msgqueue_dir = config.get('SRPM', 'msgqueue') self._srpm_msgqueue = msgqueue.MessageQueue(srpm_msgqueue_dir) self._artifactset = artifact.ArtifactSet.from_config(config) self._target_vcs = {} for target in self._artifactset.get_build_targets(): fedora_url = artifact.fedora_git_url_for_build_target(self.config, target) upstream_url = artifact.upstream_vcs_url_for_build_target(self.config, target) fedora_vcs = lame_vcs_abstraction.Vcs.new_from_spec(fedora_url) fedora_vcs.set_directory(os.path.join(mirror_dir, urllib.quote(fedora_vcs.get_url_string(), ''))) upstream_vcs = lame_vcs_abstraction.Vcs.new_from_spec(upstream_url) upstream_vcs.set_directory(os.path.join(mirror_dir, urllib.quote(upstream_vcs.get_url_string(), ''))) self._target_vcs[target] = (fedora_vcs, upstream_vcs) self._srcdir = config.get('SRPM', 'srpm_dir') if not os.path.isdir(self._srcdir): os.makedirs(self._srcdir) self._srcrepo = versioned_repos.VersionedRepo(self._srcdir) def start(self): self._vcs_msgqueue.connect(self._on_vcs_message) def _get_base_rpmbuild_args(self, src_dirpath): return ['--define', '_sourcedir ' + src_dirpath, '--define', '_specdir ' + src_dirpath, '--define', '_builddir ' + src_dirpath, '--define', '_srcrpmdir ' + src_dirpath, '--define', '_rpmdir ' + src_dirpath] def _extract_specfile_from_srpm(self, srpm_path, destpath): tmpdir = tempfile.mkdtemp('', 'extract-srpm-spec-tmp') name = rpmutils.get_rpm_name(os.path.basename(srpm_path)) task_logpath = subtask.prepare_task_logfile('extract-srpm-%s' % (name, )) logf = open(task_logpath, 'w') devnull = open(os.devnull, 'w') rpm2cpio = subprocess.Popen(['rpm2cpio', srpm_path], stdout=subprocess.PIPE, stderr=logf) cpio = subprocess.Popen(['cpio', '--quiet', '-i', '*.spec'], cwd=tmpdir, stdin=rpm2cpio.stdout, stdout=devnull, stderr=devnull) cpio.wait() for filename in os.listdir(tmpdir): if filename.endswith('.spec'): shutil.move(os.path.join(tmpdir, filename), destpath) shutil.rmtree(tmpdir) return True shutil.rmtree(tmpdir) return False def _targets_for_vcs_url(self, url): targets = [] for iter_target,(fedora_vcs, upstream_vcs) in self._target_vcs.iteritems(): fedora_url = fedora_vcs.get_url_string() is_fedora = (url == fedora_url) if not is_fedora: upstream_url = upstream_vcs.get_url_string() if not url == upstream_url: continue targets.append(iter_target) return targets def _on_vcs_message(self, q, messages): msg_list = list(messages) num_msgs = len(msg_list) logging.debug("Starting processing of %d messages" % (num_msgs, )) updated_vcs_urls = set() for msg in msg_list: url = msg.payload['url'] updated_vcs_urls.add(url) q.consume(msg) logging.debug("Creating SRPMs for VCS urls: %r" % (updated_vcs_urls, )) # This is a bit gross; we find the module-os pairs we need to rebuild, # disregarding architecture. Really, we should change BuildTarget to # just be module name, and have it be scoped to artifact. non_arch_os_modules = set() for url in updated_vcs_urls: targets = self._targets_for_vcs_url(url) if len(targets) == 0: logging.warn("Couldn't find target for VCS url %r" % (url, )) continue non_arch_os_modules.add((targets[0].module, targets[0].os)) work_dir = tempfile.mkdtemp('.tmp', 'srpm-builder') updated_srpms = set() updated_srpm_names = set() for (iter_module, iter_os) in non_arch_os_modules: found = False # Now find a target (disregarding arch) for each one, which gives us a ref # info the respective VCSes. for target,(fedora_vcs, upstream_vcs) in self._target_vcs.iteritems(): if target.module == iter_module and target.os == iter_os: srpm = self._create_srpm(work_dir, target.module, target.os, fedora_vcs, upstream_vcs) updated_srpms.add(srpm) updated_srpm_names.add(rpmutils.get_rpm_name(os.path.basename(srpm))) found = True break assert found commitid = self._srcrepo.commit_sync(updated_srpms) logging.info("New repository revision %d updates SRPMs=%r" % (commitid, updated_srpm_names)) shutil.rmtree(work_dir) self._srpm_msgqueue.append(msgqueue.Message(None, {'type': 'srpm'}, {'version': commitid})) logging.debug("Processed %d messages successfully" % (num_msgs, )) def _create_srpm(self, work_dir, module, os_name, fedora_vcs, upstream_vcs): logging.debug("Creating new SRPM for %s-%s in %r" % (module, os_name, work_dir)) fedora_dir = os.path.join(work_dir, module) fedora_vcs.export_directory('HEAD', fedora_dir, sys.stderr) specname = module + '.spec' target_spec_path = os.path.join(fedora_dir, specname) target_spec_obj = spec.Spec(target_spec_path) latest_repopath = self._srcrepo.get_latest_path() latest_repo = self._srcrepo.get_latest_version() orig_spec_path = None if latest_repo: for rpmpath in latest_repo.iter_rpms(): filename = os.path.basename(rpmpath) assert filename.endswith('.src.rpm') name = rpmutils.get_rpm_name(filename) if name == module: orig_spec_path = os.path.join(work_dir, specname) if not self._extract_specfile_from_srpm(rpmpath, orig_spec_path): raise ValueError("Failed to extract specfile from %r" % (rpmpath, )) break if orig_spec_path is not None: # Okay, we have a previous build, so now we "merge" by taking everything # from the new spec file, except we use the Version/Release from the old # one. This is necessary to pacify RPM's insistence on globally # ordering versions, rather than simply having repository build versions. orig_spec_obj = spec.Spec(orig_spec_path) orig_version = orig_spec_obj.get_key('Version') orig_release = orig_spec_obj.get_key('Release') logging.info("Will update %r from version=%r release=%r" % (module, orig_version, orig_release)) target_spec_obj.set_key('Version', orig_version) target_spec_obj.set_key('Release', orig_release) target_spec_obj.save() exec_basedir = os.path.dirname(sys.argv[0]) args = [os.path.abspath(os.path.join(exec_basedir, 'rpmci-spec-vcs')), '--vcsdir=' + upstream_vcs.get_directory(), 'set-revision', 'HEAD'] subtask.spawn_sync('rpmci-spec-vcs-%s' % (module, ), args, cwd=fedora_dir) target_spec_obj = spec.Spec(target_spec_path) new_version = target_spec_obj.get_key('Version') new_release = target_spec_obj.get_key('Release') logging.info("Updated %r to version=%r release=%r" % (module, new_version, new_release)) args = ['rpmbuild'] args.extend(self._get_base_rpmbuild_args(fedora_dir)) args.extend(['-bs', specname]) subtask.spawn_sync('rpmbuild-srpm-%s' % (module, ), args, cwd=fedora_dir) srpm_path = None srpm_basename = None for filename in os.listdir(fedora_dir): if filename.endswith('.src.rpm'): srpm_basename = filename srpm_path = os.path.join(fedora_dir, filename) break assert srpm_path is not None return srpm_path def main(): if hasattr('glib', 'threads_init'): glib.threads_init() opts = optparse.OptionParser("usage: %prog [options]") opts.add_option('-c', '--config', dest='config', help="Path to configuration file") opts.add_option('', '--debug', action='store_true', help="Print verbose debugging") (options, args) = opts.parse_args() if options.config is None: print "Must specify --config" sys.exit(1) config = SafeConfigParser({'home': os.environ['HOME']}) config.read(options.config) level = logging.DEBUG if options.debug else logging.INFO logging.basicConfig(stream=sys.stderr, level=level) subtask.global_configure(config) builder = SRPMBuilder(options, config) builder.start() logging.info("Awaiting events") loop = glib.MainLoop() loop.run()