#!/usr/bin/python2 # Copyright 2015 Lars Wirzenius # # 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 3 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, see . # # =*= License: GPL-3+ =*= import glob import os import tempfile import cliapp class App(cliapp.Application): def add_settings(self): self.settings.boolean( ['ci'], 'this is a CI build (affects version numbering etc)') self.settings.boolean( ['release'], 'this is a release build') self.settings.string( ['results'], 'Put created files in DIR', default='.', metavar='DIR') self.settings.string( ['upstream-name'], 'Assume the upstream project is NAME', metavar='NAME') self.settings.string( ['upstream-version'], 'Assume the upstream version is VERSION', metavar='VERSION') self.settings.string( ['full-version'], 'Assume the full version (upstream + Debian) is VERSION', metavar='VERSION') self.settings.string( ['buildno'], 'this is CI build number NUMBER', metavar='NUMBER') self.settings.string( ['debian-release'], 'build for target running Debian release VERSION ' '(appended to Debian version)', metavar='VERSION') self.settings.string( ['debian-codename'], 'set upload target in debian/changelog to DISTRO', metavar='DISTRO') self.settings.boolean( ['build-arch-all-also'], 'build architecture independent packages in addition to ' 'architecture dependent ones?') self.settings.string( ['pbuilder-tgz'], 'Use FILE as the pbuilder basetgz tarball', metavar='FILE') self.settings.boolean( ['test-mode'], 'just pretend to do things that would require root') def process_args(self, args): ci = self.settings['ci'] release = self.settings['ci'] if ci != release: raise cliapp.AppException( 'Use MUST use exactly one of --ci, --release') self._combine_version_options() if not self.settings['upstream-name']: self._guess_upstream_name() if not self.settings['upstream-version']: self._guess_upstream_version() if not self.settings['full-version']: self._guess_full_version() if self.settings['ci']: self._mangle_versions_for_ci_build() elif self.settings['release']: self._mangle_versions_for_release_build() return cliapp.Application.process_args(self, args) def _combine_version_options(self): upstream = self.settings['upstream-version'] full = self.settings['full-version'] if upstream and not full: full = upstream + '-1' elif full and not upstream: upstream = '-'.join(full.split('-')[:-1]) self.settings['upstream-version'] = upstream self.settings['full-version'] = full def _guess_upstream_name(self): line = cliapp.runcmd( ['sed', '-n', '/^Source: /s///p', 'debian/control']) self.settings['upstream-name'] = line.strip() def _report(self, msg, **kwargs): self.output.write('cleanly: ' + msg.format(**kwargs) + '\n') def _guess_upstream_version(self): full_version = self._get_full_version_from_debian_changelog() if '-' in full_version: upstream_version = '-'.join(full_version.split('-')[:-1]) else: upstream_version = full_version self.settings['upstream-version'] = upstream_version def _get_full_version_from_debian_changelog(self): line = cliapp.runcmd( ['dpkg-parsechangelog'], ['sed', '-n', '/^Version: /s///p']) return line.strip() def _guess_full_version(self): full_version = self._get_full_version_from_debian_changelog() self.settings['full-version'] = full_version def _mangle_versions_for_ci_build(self): assert self.settings['ci'] if not self.settings['buildno']: raise cliapp.AppException('--buildno required') if not self.settings['debian-release']: raise cliapp.AppException('--debian-release required') ci_suffix = '.0ci{buildno}'.format(buildno=self.settings['buildno']) debian_version = '-1.{release}'.format( release=self.settings['debian-release']) self.settings['upstream-version'] += ci_suffix self.settings['full-version'] = ( self.settings['upstream-version'] + debian_version) def _mangle_versions_for_release_build(self): assert self.settings['release'] if not self.settings['debian-release']: raise cliapp.AppException('--debian-release required') if self.settings['debian-release'] == 'unstable': return debian = self.settings['full-version'].split('-')[-1] self.settings['full-version'] = '{}-{}.{}'.format( self.settings['upstream-version'], debian, self.settings['debian-release']) def cmd_get_name(self, args): self.settings.require('upstream-name') self.output.write('%s\n' % self.settings['upstream-name']) def cmd_get_upstream_version(self, args): self.settings.require('upstream-version') self.output.write('%s\n' % self.settings['upstream-version']) def cmd_get_full_version(self, args): self.settings.require('full-version') self.output.write('%s\n' % self.settings['full-version']) def cmd_tarball(self, args): self.settings.require('upstream-name') self.settings.require('upstream-version') filename = self._get_upstream_tarball_pathname() with open(filename, 'w') as f: cliapp.runcmd( ['git', 'archive', '--prefix', self._construct_tar_prefix() + '/', 'HEAD'], ['xz', '-9'], stdout=f) def _get_upstream_tarball_pathname(self): upstream_name = self.settings['upstream-name'] upstream_version = self.settings['upstream-version'] return os.path.join( self.settings['results'], '%s-%s.tar.xz' % (upstream_name, upstream_version)) def cmd_dsc(self, args): self._report('Create Debian source package') self.settings.require('upstream-name') self.settings.require('upstream-version') self.settings.require('results') upstream_tarball = self._get_upstream_tarball_pathname() tempdir = tempfile.mkdtemp() debian_orig_tarball = os.path.join( tempdir, self._construct_debian_orig_tarball_basename()) self._report('Create orig.tar.xz from upstream tarball') cliapp.runcmd(['cp', upstream_tarball, debian_orig_tarball]) self._report('Copy source tree to temporary directory') srcdir = os.path.join(tempdir, 'src') os.mkdir(srcdir) cliapp.runcmd(['cp', '-a', '.', srcdir]) distribution = self.settings['debian-codename'] if self.settings['ci']: self._report('Add new debian/changelog entry for CI build') cliapp.runcmd( ['dch', '-v', self.settings['full-version'], 'CI build'], cwd=srcdir) distribution += '-ci' cliapp.runcmd( ['dch', '-r', '--force-distribution', '-D', distribution, ''], cwd=srcdir) elif self.settings['debian-codename'] != 'unstable': self._report('Add new debian/changelog entry') cliapp.runcmd( ['dch', '-v', self.settings['full-version'], 'Binary build'], cwd=srcdir) cliapp.runcmd( ['dch', '-r', '--force-distribution', '-D', distribution, ''], cwd=srcdir) else: self._report( 'Release build for unstable: no debian/changelog update') self._report('Build dsc') with open('/dev/null') as f: cliapp.runcmd( ['dpkg-buildpackage', '-S', '-sa', '-us', '-uc', '-i'], cwd=srcdir, stdin=f, stdout=None, stderr=None) self._report('Clean up') cliapp.runcmd(['rm', '-rf', srcdir]) cliapp.runcmd( ['mv'] + glob.glob(tempdir + '/*') + [self.settings['results']]) os.rmdir(tempdir) def _find_unpacked_srcdir(self, parent): for basename in os.listdir(parent): pathname = os.path.join(parent, basename) if os.path.isdir(pathname): return pathname raise cliapp.AppException('Cannot find srcdir in %s' % parent) def _construct_debian_orig_tarball_basename(self): upstream_name = self.settings['upstream-name'] upstream_version = self.settings['upstream-version'] return '%s_%s.orig.tar.xz' % (upstream_name, upstream_version) def _construct_tar_prefix(self): upstream_name = self.settings['upstream-name'] upstream_version = self.settings['upstream-version'] return '%s-%s' % (upstream_name, upstream_version) def cmd_deb(self, args): self._report('Build Debian binary package') self.settings.require('upstream-name') self.settings.require('full-version') self.settings.require('results') self.settings.require('pbuilder-tgz') result = self.settings['results'] dsc_path = self._construct_dsc_pathname() if self.settings['test-mode']: self._report('Buildling in test mode, thus faking the build') deb_path = os.path.join( result, '%s_%s_all.deb' % ( self.settings['upstream-name'], self.settings['full-version'])) cliapp.runcmd(['touch', deb_path]) else: self._report('Update pbuilder basetgz') tgz = self.settings['pbuilder-tgz'] argv_prefix = [ 'sudo', '-E', 'env', 'HOME=/root', 'pbuilder', ] common_opts = [ '--aptcache', '', '--allow-untrusted', '--basetgz', tgz, ] cliapp.runcmd( argv_prefix + ['--update'] + common_opts, stdout=None, stderr=None) self._report('Find out of dpkg-buildpackage has --check-option') output = cliapp.runcmd(['dpkg-buildpackage', '--help']) has_check_option = '---check-option' in output self._report('Build the binary packages') opts = [ '--build', '--buildresult', result, ] if has_check_option: opts.extend([ '--debbuildopts', '--check-option=-Xfoo,bad-distribution-in-changes-file,changed-by-address-malformed', ]) if self.settings['build-arch-all-also']: opts += ['--debbuildopts', '-b'] else: opts += ['--debbuildopts', '-B'] cliapp.runcmd( argv_prefix + opts + common_opts + [dsc_path], stdout=None, stderr=None) def _construct_dsc_pathname(self): results = self.settings['results'] upstream_name = self.settings['upstream-name'] full_version = self.settings['full-version'] dsc_basename = '%s_%s.dsc' % (upstream_name, full_version) return os.path.join(results, dsc_basename) if __name__ == '__main__': App().run()