519 lines
20 KiB
Python
Executable File
519 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
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 projet_name not in project_files:
|
|
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'], 0o755)
|
|
|
|
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, 0o755)
|
|
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'].split(':', 1)[-1],
|
|
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 package['source_name'] in settings.MANUAL_TESTING_REPOSITORIES:
|
|
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 dist in package_repos 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(e, file=sys.stderr)
|
|
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(e, file=sys.stderr)
|
|
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'], 0o755)
|
|
|
|
# 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()
|
|
|