eobuilder/eobuilder-ctl

519 lines
20 KiB
Python
Executable File

#!/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()