#!/usr/bin/env python import atexit import re import shutil import subprocess import sys import tarfile import time import os from eobuilder import settings, VERSION, init from eobuilder.changelog import changelog_from_git from eobuilder.cmdline import parse_cmdline, error, cat, touch, call, output def rm_recursive(path): if os.path.exists(path): shutil.rmtree(path) def smart_cleaning(files_path): now = time.time() project_files = {} for file_path in files_path: project_name = os.path.basename(file_path).split('_')[0] if not project_files.has_key(project_name): project_files[project_name] = [] project_files[project_name].append(file_path) for project in project_files.iterkeys(): nb_versions = len(project_files[project]) if nb_versions > settings.MIN_PACKAGE_VERSIONS: project_files[project] = \ sorted(project_files[project], key=lambda x: os.stat(x).st_mtime) for filename in project_files[project]: if nb_versions > settings.MIN_PACKAGE_VERSIONS and \ os.stat(filename).st_mtime < now - settings.MIN_AGE * 86400: os.remove(filename) nb_versions -= 1 def clean(method): print "+ Cleanning %s" % method if method == "all": shutil.rmtree(settings.ORIGIN_PATH) shutil.rmtree(settings.PBUILDER_RESULT) shutil.rmtree(settings.GIT_PATH) shutil.rmtree(settings.LOCK_PATH) elif method == "git": shutil.rmtree(settings.GIT_PATH) elif method == "deb": shutil.rmtree(settings.PBUILDER_RESULT) elif method == "archives": shutil.rmtree(settings.ORIGIN_PATH) elif method == "locks": shutil.rmtree(settings.LOCK_PATH) elif method == "smart": results_files = [] origin_files = [os.path.join(settings.ORIGIN_PATH, f) \ for f in os.listdir(settings.ORIGIN_PATH)] for root, dirs, files in os.walk(settings.PBUILDER_RESULT): for fname in files: results_files.append(os.path.join(root, fname)) smart_cleaning(results_files) smart_cleaning(origin_files) now = time.time() for root, dirs, files in os.walk(settings.PBUILDER_RESULT): for fname in files: fname = os.path.join(root, fname) ext = os.path.splitext(fname) if ext == "build" and \ os.stat(fname).st_mtime < now - 365 * 86400: os.remove(fname) else: error("Cleanning: unknow '%s' option" % method) def get_project_infos(git_project_path, cmd_options): """ return a dict with project informations """ os.chdir(git_project_path) results = { 'name': '', 'version': '', 'fullname': '', 'ac_version': '', 'build_dir': '', 'build_branch': cmd_options.branch, 'commit_number': '', 'git_path': git_project_path, } if os.path.exists("setup.py"): # Hack to support setup_requires output("python setup.py --help") results['name'] = output("python setup.py --name 2> /dev/null")[:-1] results['version'] = output("python setup.py --version 2> /dev/null")[:-1] results['fullname'] = output("python setup.py --fullname 2> /dev/null")[:-1] elif os.path.exists("configure.ac"): call("./autogen.sh") call('make all') results['name'] = output( "./configure --version | head -n1 | sed 's/ configure.*//'" )[:-1] results['ac_version'] = output( "./configure --version | head -n1 | sed 's/.* configure //'" )[:-1] results['version'] = results['ac_version'].replace('-', '.') results['fullname'] = results['name'] elif os.path.exists("Makefile"): results['name'] = output("make name")[:-1] results['version'] = output("make version")[:-1] results['fullname'] = output("make fullname")[:-1] else: error("Unsupported project type", exit_code=2) results['build_dir'] = os.path.join( settings.EOBUILDER_TMP, results['name'] ) atexit.register(rm_recursive, results['build_dir']) results['commit_number'] = output("git rev-parse HEAD")[:-1] results['lock_path'] = os.path.join(settings.LOCK_PATH, results['name']) current_tag = output( "git describe --abbrev=0 --tags --match=v*", exit_on_error=False) if current_tag: results['current_tag'] = current_tag[1:-1] else: results['current_tag'] = "0.0" return results def prepare_build(dist, project, cmd_options, new): """ Create origin archive, update git and Debian changelog """ package = { 'repository': settings.DEFAULT_UNSTABLE_REPOSITORIES[dist], 'copy_in_testing': True, 'version': '', 'source_name': '', 'names': [] } if cmd_options.hotfix: package['repository'] = settings.HOTFIX_REPOSITORIES[dist] os.chdir(project['git_path']) build_branch = cmd_options.branch debian_folder = cmd_options.debian_folder if os.path.isdir('debian-' + dist) and debian_folder == 'debian': debian_folder = 'debian-' + dist debian_branch = None if not os.path.isdir(debian_folder): debian_branch = "debian" debian_folder = "debian" branches = output("git branch -r -l") if debian_branch == "debian" and "debian-%s" % dist in branches: debian_branch = "debian-" + dist if not 'origin/%s' % debian_branch in output("git branch -r -l"): print '!!! WARNING: cannot build for dist %s, no debian directory found' % dist return print "!!! WARNING obsolete: using a branch for debian/ packaging" print "+ Updating Debian branch for %s" % dist call("git checkout --quiet %s" % debian_branch) call("git pull") else: print "+ Building from %s debian folder" % debian_folder for r in cmd_options.repositories: repo = r.split(':') if repo[0] == dist: package["repository"] = repo[1] package["copy_in_testing"] = False # get package source name control_file = os.path.join(debian_folder, 'control') package['names'] = re.findall(r"Package\s*:\s*(.*?)\n", cat(control_file)) package['source_name'] = re.search(r"^Source\s*:\s*(.*?)\n", cat(control_file), re.MULTILINE ).group(1) # build tarball origin_archive = os.path.join(settings.ORIGIN_PATH, "%s_%s.orig.tar.bz2" % (package['source_name'], project['version'])) if not os.path.exists(origin_archive): print "+ Generating origin tarball ..." os.chdir(project['git_path']) call("git checkout --quiet %s" % build_branch) if os.path.exists('setup.py'): call("python setup.py clean --all") call("python setup.py sdist --formats=bztar") shutil.move("dist/%s.tar.bz2" % project['fullname'], origin_archive) elif os.path.exists('./configure.ac'): call("make dist-bzip2") shutil.move("%s-%s.tar.bz2" % \ (project['name'], project['ac_version']), origin_archive) elif os.path.exists('Makefile'): call("make dist-bzip2") shutil.move("sdist/%s.tar.bz2" % project['fullname'], origin_archive) else: error('Unsupported project type', project['build_dir'], exit_code=2) last_version_file = os.path.join(project['lock_path'], "%s_%s_%s.last_version" % (project['name'], package['repository'], build_branch.replace('/', '_')) ) debian_changelog = os.path.join(debian_folder, 'changelog') if os.path.exists(last_version_file): last_debian_package_version = cat(last_version_file) else: last_debian_package_version = re.search(r"^Version:\s(.*?)$", output("dpkg-parsechangelog -l%s" % debian_changelog), re.MULTILINE).group(1) last_version = last_debian_package_version.split('-')[0] package['version'] = last_debian_package_version if cmd_options.native: debian_revision_number = '' else: debian_revision_number = '-1' if last_version == project['version'] and new \ and "~eob" in last_debian_package_version: new_inc = int(last_debian_package_version.rsplit('+', 1)[-1]) + 1 version_suffix = "%s~eob%s+%s" % (debian_revision_number, settings.DEBIAN_VERSIONS[dist], new_inc) else: version_suffix = "%s~eob%s+1" % (debian_revision_number, settings.DEBIAN_VERSIONS[dist]) call("git checkout --quiet %s" % build_branch) changelog = '\n'.join(changelog_from_git(package['source_name'], version_suffix, project['git_path'], package['repository'], epoch=cmd_options.epoch)) if changelog: if not os.path.isdir(debian_folder): call("git checkout --quiet %s" % debian_branch) debian_generated_changelog_filename = debian_changelog + '.generated' with open(debian_generated_changelog_filename, 'w+') as f: f.write(changelog) package['version'] = re.search(r"^Version:\s(.*?)$", output("dpkg-parsechangelog -l%s" % debian_generated_changelog_filename), re.MULTILINE).group(1) os.unlink(debian_generated_changelog_filename) else: # changelog couldn't be generated, this happens with checkouts from # dgit, at least. package['version'] = '' if os.path.exists(project['build_dir']): shutil.rmtree(project['build_dir']) os.makedirs(project['build_dir'], 0755) if package['version'].split('-')[0].split(':')[-1] == project['version']: # the generated changelog has the right version number, use it. good_changelog_contents = changelog else: # wrong version number, in that case we add an arbitrary new entry # to the existing changelog package['version'] = '%s%s' % (project['version'], version_suffix) call('dch "Eobuilder version" -v %s --distribution %s \ --force-bad-version --force-distribution --changelog %s' % \ ('%s:%s' % (cmd_options.epoch, package['version']) if cmd_options.epoch else package['version'], package["repository"], debian_changelog)) good_changelog_contents = open(debian_changelog).read() build_file = os.path.join(project['lock_path'], "%s_%s_%s_%s.build" % (project['name'], package['version'], package['repository'], build_branch.replace('/', '_')) ) if os.path.exists(build_file): print "+ Already built for %s !" % dist return package print "+ Preparing Debian build (%s %s) ..." % (package['source_name'], package['version']) if debian_branch: call("git checkout --quiet %s" % debian_branch) os.chdir(project['build_dir']) project_build_path = os.path.join(project['build_dir'], "%s-%s" % (project['name'], project['version'])) shutil.copy(origin_archive, project['build_dir']) tar = tarfile.open('%s_%s.orig.tar.bz2' % \ (package['source_name'], project['version']), 'r:bz2') tar.extractall() tar.close() if os.path.exists("%s/debian" % project_build_path): shutil.rmtree("%s/debian" % project_build_path) shutil.copytree(os.path.join(project['git_path'], debian_folder), "%s/debian" % project_build_path) with open(os.path.join(project_build_path, 'debian', 'changelog'), 'w') as f: f.write(good_changelog_contents) return package def build_project(dist, arch, project, package, new): pbuilder_project_result = os.path.join(settings.PBUILDER_RESULT, '%s-%s' % (dist, arch)) project_build_path = os.path.join(project['build_dir'], "%s-%s" % (project['name'], project['version'])) if not os.path.exists(pbuilder_project_result): os.makedirs(pbuilder_project_result, 0755) os.chdir(project['lock_path']) source_build = os.path.join(project['lock_path'], "%s_%s_%s_%s_source.build" % (project['name'], project['version'], package['repository'], project['build_branch'].replace('/', '_')) ) bin_build = os.path.join(project['lock_path'], "%s_%s_%s_%s_%s.build" % (project['name'], package['version'], package['repository'], project['build_branch'].replace('/', '_'), arch) ) print 'SOURCE_BUILD:', source_build if os.path.exists(source_build): source_opt = '-b' else: source_opt = '-sa' if new == 0 and os.path.exists(bin_build): print "+ Already build !" return os.chdir(project_build_path) print "+ Building %s %s %s %s" % \ (project['name'], project['version'], dist, arch) call('DIST=%s ARCH=%s pdebuild --use-pdebuild-internal --architecture %s --debbuildopts "%s"' % \ (dist, arch, arch, source_opt)) print "+ Lock build" touch(bin_build) if not os.path.exists(source_build): touch(source_build) def send_packages(dist, arch, project, package, last_tag): stamp_file = os.path.join(project['lock_path'], '%s_%s_%s_%s_%s.upload' % ( project['name'], package['version'], package['repository'], arch, project['build_branch'].replace('/', '_'))) if os.path.exists(stamp_file): print '+ Already uploaded' return print "+ Sending package..." pbuilder_project_result = os.path.join(settings.PBUILDER_RESULT, '%s-%s' % (dist, arch)) os.chdir(pbuilder_project_result) call("dput -u %s %s_%s_%s.changes" % \ (package['repository'], package['source_name'], package['version'], arch) ) print "+ Updating repository ..." call('ssh root@%s "/etc/cron.hourly/process-incoming"' % \ settings.REPOSITORY_URL) old_version = tuple(int(d) for d in last_tag.split('.')) new_version = tuple(int(d) for d in project['current_tag'].split('.')) if new_version > old_version and \ project['current_tag'] == project['version']: print "New tag detected : %s" % project['current_tag'] if settings.MANUAL_TESTING_REPOSITORIES.has_key(package['source_name']): package_repos = settings.MANUAL_TESTING_REPOSITORIES[package['source_name']] else: package_repos = settings.DEFAULT_TESTING_REPOSITORIES packages = package['names'] + [package['source_name']] packages = " ".join(packages) if package_repos.has_key(dist) and package['copy_in_testing']: for repo in package_repos[dist]: print "+ Copy %s packages to %s repository (%s)" % (package['source_name'], repo, dist) call('ssh root@%s "/usr/bin/reprepro -b /var/vhosts/deb.entrouvert.org copy %s %s %s"'\ % (settings.REPOSITORY_URL, repo, package['repository'], packages)) call('ssh root@%s /usr/local/bin/update-deb-repo-html' % \ settings.REPOSITORY_URL) open(stamp_file, 'w').close() def clean_git_on_exit(git_project_path): if not os.path.exists(git_project_path): return os.chdir(git_project_path) call("git stash --quiet") changelog_tmp = os.path.join( git_project_path, "debian", "changelog.git" ) if os.path.exists(changelog_tmp): os.remove(changelog_tmp) def setup_git_tree(project_name, options): git_project_path = os.path.join(settings.GIT_PATH, os.path.basename(project_name)) if options.branch.startswith('wip/'): if os.path.exists(git_project_path): shutil.rmtree(git_project_path) if os.path.exists(git_project_path): existing_tree = True os.chdir(git_project_path) try: subprocess.check_call(['git', 'checkout', '--quiet', options.branch]) subprocess.check_call(['git', 'reset', '--hard', 'origin/%s' % options.branch]) except subprocess.CalledProcessError as e: print >> sys.stderr, e shutil.rmtree(git_project_path) return setup_git_tree(project_name, options) else: existing_tree = False os.chdir(settings.GIT_PATH) if project_name.startswith('/'): call("git clone %s" % project_name) else: call("git clone %s/%s.git" % \ (settings.GIT_REPOSITORY_URL, project_name)) os.chdir(git_project_path) try: subprocess.check_call(['git', 'checkout', '--quiet', options.branch]) subprocess.check_call(['git', 'pull']) subprocess.check_call(['git', 'submodule', 'init']) subprocess.check_call(['git', 'submodule', 'update']) except subprocess.CalledProcessError as e: if existing_tree: print >> sys.stderr, e shutil.rmtree(git_project_path) return setup_git_tree(project_name, options) raise def main(): options, args = parse_cmdline() for method in options.cleaning: clean(method) if options.cleaning: sys.exit(0) init() project_name = args[0] git_project_path = os.path.join(settings.GIT_PATH, os.path.basename(project_name)) atexit.register(clean_git_on_exit, git_project_path) if options.branch.startswith('origin/'): # normalize without origin/ options.branch = options.branch[len('origin/'):] existing_tree = os.path.exists(git_project_path) if existing_tree: os.chdir(git_project_path) last_tag = output("git describe --abbrev=0 --tags --match=v*", exit_on_error=False) if last_tag: last_tag = last_tag[1:-1] else: last_tag = "0.0" else: last_tag = "0.0" setup_git_tree(project_name, options) project = get_project_infos(git_project_path, options) if not os.path.exists(project['lock_path']): os.mkdir(project['lock_path'], 0755) # compare revision between last build and now to determine if something is really new new = 1 current_revision = output("git rev-parse HEAD", True).strip() last_branch_revision_file_path = os.path.join( project['lock_path'], "%s_%s.last_revision" % ( project['name'], options.branch.replace('/', '_'))) try: with open(last_branch_revision_file_path) as f: last_branch_revision = f.read().strip() except IOError: pass else: if current_revision == last_branch_revision: new = 0 if options.force and not new: print "+ Warning force a new build" new = 1 for dist in options.distrib: os.chdir(git_project_path) call("git checkout --quiet %s" % options.branch) package = prepare_build(dist, project, options, new) if package: for arch in options.architectures: build_project(dist, arch, project, package, new) send_packages(dist, arch, project, package, last_tag) print "+ Add a build file to lock new build for %s" % dist touch(os.path.join(project['lock_path'], "%s_%s_%s_%s.build" % (project['name'], package['version'], package['repository'], options.branch.replace('/', '_')) )) last_version_file = os.path.join(project['lock_path'], "%s_%s_%s.last_version" % (project['name'], package['repository'], options.branch.replace('/', '_')) ) with open(last_version_file, 'w+') as f: f.write(package['version']) # keep current revision for next build with open(last_branch_revision_file_path, 'w+') as f: f.write(current_revision) if __name__ == "__main__": main()