diff options
| author | Carl Hetherington <cth@carlh.net> | 2015-12-08 23:19:23 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2015-12-08 23:19:23 +0000 |
| commit | ca782321356fadb3be8df7819b48a7baf7cec6d9 (patch) | |
| tree | ec85a172fa023aa40675e44f70263a616c414cf3 | |
| parent | db7fb903bc236c34ee05ea44a6274bf7afee6559 (diff) | |
Split with some tests.
33 files changed, 1565 insertions, 960 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03c6c50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +test +*.pyc +*~ +cdist.egg-info @@ -0,0 +1,2 @@ +remove need for os.path.abspath in cscripts +tidy build/build_dependencies stuff @@ -1,960 +0,0 @@ -#!/usr/bin/python - -# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> -# -# 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 2 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, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -import os -import sys -import shutil -import glob -import tempfile -import argparse -import datetime -import subprocess -import re -import copy -import inspect - -TEMPORARY_DIRECTORY = '/var/tmp' - -class Error(Exception): - def __init__(self, value): - self.value = value - def __str__(self): - return self.value - def __repr__(self): - return str(self) - -class Trees: - """ - Store for Tree objects which re-uses already-created objects - and checks for requests for different versions of the same thing. - """ - - def __init__(self): - self.trees = [] - - def get(self, name, specifier, target): - for t in self.trees: - if t.name == name and t.specifier == specifier and t.target == target: - return t - elif t.name == name and t.specifier != specifier: - raise Error('conflicting versions of %s requested (%s and %s)' % (name, specifier, t.specifier)) - - nt = Tree(name, specifier, target) - self.trees.append(nt) - return nt - -class Globals: - quiet = False - command = None - trees = Trees() - -globals = Globals() - - -# -# Configuration -# - -class Option(object): - def __init__(self, key, default=None): - self.key = key - self.value = default - - def offer(self, key, value): - if key == self.key: - self.value = value - -class BoolOption(object): - def __init__(self, key): - self.key = key - self.value = False - - def offer(self, key, value): - if key == self.key: - self.value = (value == 'yes' or value == '1' or value == 'true') - -class Config: - def __init__(self): - self.options = [ Option('linux_chroot_prefix'), - Option('windows_environment_prefix'), - Option('mingw_prefix'), - Option('git_prefix'), - Option('osx_build_host'), - Option('osx_environment_prefix'), - Option('osx_sdk_prefix'), - Option('osx_sdk'), - Option('parallel', 4) ] - - try: - f = open('%s/.config/cdist' % os.path.expanduser('~'), 'r') - while True: - l = f.readline() - if l == '': - break - - if len(l) > 0 and l[0] == '#': - continue - - s = l.strip().split() - if len(s) == 2: - for k in self.options: - k.offer(s[0], s[1]) - except: - raise - - def get(self, k): - for o in self.options: - if o.key == k: - return o.value - - raise Error('Required setting %s not found' % k) - - def set(self, k, v): - for o in self.options: - o.offer(k, v) - -config = Config() - -# -# Utility bits -# - -def log(m): - if not globals.quiet: - print '\x1b[33m* %s\x1b[0m' % m - -def scp_escape(n): - s = n.split(':') - assert(len(s) == 1 or len(s) == 2) - if len(s) == 2: - return '%s:"\'%s\'"' % (s[0], s[1]) - else: - return '\"%s\"' % s[0] - -def copytree(a, b): - log('copy %s -> %s' % (scp_escape(b), scp_escape(b))) - command('scp -r %s %s' % (scp_escape(a), scp_escape(b))) - -def copyfile(a, b): - log('copy %s -> %s' % (scp_escape(a), scp_escape(b))) - command('scp %s %s' % (scp_escape(a), scp_escape(b))) - -def makedirs(d): - if d.find(':') == -1: - os.makedirs(d) - else: - s = d.split(':') - command('ssh %s -- mkdir -p %s' % (s[0], s[1])) - -def rmdir(a): - log('remove %s' % a) - os.rmdir(a) - -def rmtree(a): - log('remove %s' % a) - shutil.rmtree(a, ignore_errors=True) - -def command(c): - log(c) - r = os.system(c) - if (r >> 8): - raise Error('command %s failed' % c) - -def command_and_read(c): - log(c) - p = subprocess.Popen(c.split(), stdout=subprocess.PIPE) - f = os.fdopen(os.dup(p.stdout.fileno())) - return f - -def read_wscript_variable(directory, variable): - f = open('%s/wscript' % directory, 'r') - while True: - l = f.readline() - if l == '': - break - - s = l.split() - if len(s) == 3 and s[0] == variable: - f.close() - return s[2][1:-1] - - f.close() - return None - -def set_version_in_wscript(version): - f = open('wscript', 'rw') - o = open('wscript.tmp', 'w') - while True: - l = f.readline() - if l == '': - break - - s = l.split() - if len(s) == 3 and s[0] == "VERSION": - print "Writing %s" % version - print >>o,"VERSION = '%s'" % version - else: - print >>o,l, - f.close() - o.close() - - os.rename('wscript.tmp', 'wscript') - -def append_version_to_changelog(version): - try: - f = open('ChangeLog', 'r') - except: - log('Could not open ChangeLog') - return - - c = f.read() - f.close() - - f = open('ChangeLog', 'w') - now = datetime.datetime.now() - f.write('%d-%02d-%02d Carl Hetherington <cth@carlh.net>\n\n\t* Version %s released.\n\n' % (now.year, now.month, now.day, version)) - f.write(c) - -def append_version_to_debian_changelog(version): - if not os.path.exists('debian'): - log('Could not find debian directory') - return - - command('dch -b -v %s-1 "New upstream release."' % version) - -def devel_to_git(git_commit, filename): - if git_commit is not None: - filename = filename.replace('devel', '-%s' % git_commit) - return filename - -class TreeDirectory: - def __init__(self, tree): - self.tree = tree - def __enter__(self): - self.cwd = os.getcwd() - os.chdir('%s/src/%s' % (self.tree.target.directory, self.tree.name)) - def __exit__(self, type, value, traceback): - os.chdir(self.cwd) - -# -# Version -# - -class Version: - def __init__(self, s): - self.devel = False - - if s.startswith("'"): - s = s[1:] - if s.endswith("'"): - s = s[0:-1] - - if s.endswith('devel'): - s = s[0:-5] - self.devel = True - - if s.endswith('pre'): - s = s[0:-3] - - p = s.split('.') - self.major = int(p[0]) - self.minor = int(p[1]) - if len(p) == 3: - self.micro = int(p[2]) - else: - self.micro = 0 - - def bump_minor(self): - self.minor += 1 - self.micro = 0 - - def bump_micro(self): - self.micro += 1 - - def to_devel(self): - self.devel = True - - def to_release(self): - self.devel = False - - def __str__(self): - s = '%d.%d.%d' % (self.major, self.minor, self.micro) - if self.devel: - s += 'devel' - - return s - -# -# Targets -# - -class Target(object): - """ - platform -- platform string (e.g. 'windows', 'linux', 'osx') - directory -- directory to work in; if None we will use a temporary directory - Temporary directories will be removed after use; specified directories will not. - """ - def __init__(self, platform, directory=None): - self.platform = platform - self.parallel = int(config.get('parallel')) - - # self.directory is the working directory - if directory is None: - self.directory = tempfile.mkdtemp('', 'tmp', TEMPORARY_DIRECTORY) - self.rmdir = True - else: - self.directory = directory - self.rmdir = False - - # Environment variables that we will use when we call cscripts - self.variables = {} - self.debug = False - - def package(self, project, checkout): - tree = globals.trees.get(project, checkout, self) - tree.build_dependencies() - tree.build(tree) - return tree.call('package', tree.version), tree.git_commit - - def test(self, tree): - tree.build_dependencies() - tree.build() - return tree.call('test') - - def set(self, a, b): - self.variables[a] = b - - def unset(self, a): - del(self.variables[a]) - - def get(self, a): - return self.variables[a] - - def append_with_space(self, k, v): - if (not k in self.variables) or len(self.variables[k]) == 0: - self.variables[k] = '"%s"' % v - else: - e = self.variables[k] - if e[0] == '"' and e[-1] == '"': - self.variables[k] = '"%s %s"' % (e[1:-1], v) - else: - self.variables[k] = '"%s %s"' % (e, v) - - def variables_string(self, escaped_quotes=False): - e = '' - for k, v in self.variables.iteritems(): - if escaped_quotes: - v = v.replace('"', '\\"') - e += '%s=%s ' % (k, v) - return e - - def cleanup(self): - if self.rmdir: - rmtree(self.directory) - -# -# Windows -# - -class WindowsTarget(Target): - def __init__(self, bits, directory=None): - super(WindowsTarget, self).__init__('windows', directory) - self.bits = bits - - self.windows_prefix = '%s/%d' % (config.get('windows_environment_prefix'), self.bits) - if not os.path.exists(self.windows_prefix): - raise Error('windows prefix %s does not exist' % self.windows_prefix) - - if self.bits == 32: - self.mingw_name = 'i686' - else: - self.mingw_name = 'x86_64' - - self.mingw_path = '%s/%d/bin' % (config.get('mingw_prefix'), self.bits) - self.mingw_prefixes = ['/%s/%d' % (config.get('mingw_prefix'), self.bits), '%s/%d/%s-w64-mingw32' % (config.get('mingw_prefix'), bits, self.mingw_name)] - - self.set('PKG_CONFIG_LIBDIR', '%s/lib/pkgconfig' % self.windows_prefix) - self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/bin/pkgconfig' % (self.directory, self.directory)) - self.set('PATH', '%s/bin:%s:%s' % (self.windows_prefix, self.mingw_path, os.environ['PATH'])) - self.set('CC', '%s-w64-mingw32-gcc' % self.mingw_name) - self.set('CXX', '%s-w64-mingw32-g++' % self.mingw_name) - self.set('LD', '%s-w64-mingw32-ld' % self.mingw_name) - self.set('RANLIB', '%s-w64-mingw32-ranlib' % self.mingw_name) - self.set('WINRC', '%s-w64-mingw32-windres' % self.mingw_name) - cxx = '-I%s/include -I%s/include' % (self.windows_prefix, self.directory) - link = '-L%s/lib -L%s/lib' % (self.windows_prefix, self.directory) - for p in self.mingw_prefixes: - cxx += ' -I%s/include' % p - link += ' -L%s/lib' % p - self.set('CXXFLAGS', '"%s"' % cxx) - self.set('CPPFLAGS', '') - self.set('LINKFLAGS', '"%s"' % link) - self.set('LDFLAGS', '"%s"' % link) - - def command(self, c): - log('host -> %s' % c) - command('%s %s' % (self.variables_string(), c)) - -class LinuxTarget(Target): - """Parent for Linux targets""" - def __init__(self, distro, version, bits, directory=None): - super(LinuxTarget, self).__init__('linux', directory) - self.distro = distro - self.version = version - self.bits = bits - - self.set('CXXFLAGS', '-I%s/include' % self.directory) - self.set('CPPFLAGS', '') - self.set('LINKFLAGS', '-L%s/lib' % self.directory) - self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:/usr/local/lib/pkgconfig' % self.directory) - self.set('PATH', '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin') - -class ChrootTarget(LinuxTarget): - """Build in a chroot""" - def __init__(self, distro, version, bits, directory=None): - super(ChrootTarget, self).__init__(distro, version, bits, directory) - # e.g. ubuntu-14.04-64 - if self.version is not None and self.bits is not None: - self.chroot = '%s-%s-%d' % (self.distro, self.version, self.bits) - else: - self.chroot = self.distro - # e.g. /home/carl/Environments/ubuntu-14.04-64 - self.chroot_prefix = '%s/%s' % (config.get('linux_chroot_prefix'), self.chroot) - - def command(self, c): - command('%s schroot -c %s -p -- %s' % (self.variables_string(), self.chroot, c)) - - -class HostTarget(LinuxTarget): - """Build directly on the host""" - def __init__(self, distro, version, bits, directory=None): - super(HostTarget, self).__init__(distro, version, bits, directory) - - def command(self, c): - command('%s %s' % (self.variables_string(), c)) - -# -# OS X -# - -class OSXTarget(Target): - def __init__(self, directory=None): - super(OSXTarget, self).__init__('osx', directory) - self.sdk = config.get('osx_sdk') - self.sdk_prefix = config.get('osx_sdk_prefix') - - def command(self, c): - command('%s %s' % (self.variables_string(False), c)) - - -class OSXSingleTarget(OSXTarget): - def __init__(self, bits, directory=None): - super(OSXSingleTarget, self).__init__(directory) - self.bits = bits - - if bits == 32: - arch = 'i386' - else: - arch = 'x86_64' - - flags = '-isysroot %s/MacOSX%s.sdk -arch %s' % (self.sdk_prefix, self.sdk, arch) - enviro = '%s/%d' % (config.get('osx_environment_prefix'), bits) - - # Environment variables - self.set('CFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags)) - self.set('CPPFLAGS', '') - self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags)) - self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags)) - self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags)) - self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/lib/pkgconfig:/usr/lib/pkgconfig' % (self.directory, enviro)) - self.set('PATH', '$PATH:/usr/bin:/sbin:/usr/local/bin:%s/bin' % enviro) - self.set('MACOSX_DEPLOYMENT_TARGET', config.get('osx_sdk')) - - def package(self, project, checkout): - raise Error('cannot package non-universal OS X versions') - - -class OSXUniversalTarget(OSXTarget): - def __init__(self, directory=None): - super(OSXUniversalTarget, self).__init__(directory) - - def package(self, project, checkout): - - for b in [32, 64]: - target = OSXSingleTarget(b, os.path.join(self.directory, '%d' % b)) - tree = globals.trees.get(project, checkout, target) - tree.build_dependencies() - tree.build() - - tree = globals.trees.get(project, checkout, self) - with TreeDirectory(tree): - return tree.call('package', tree.version), tree.git_commit - -class SourceTarget(Target): - """Build a source .tar.bz2""" - def __init__(self): - super(SourceTarget, self).__init__('source') - - def command(self, c): - log('host -> %s' % c) - command('%s %s' % (self.variables_string(), c)) - - def cleanup(self): - rmtree(self.directory) - - def package(self, project, checkout): - tree = globals.trees.get(project, checkout, self) - with TreeDirectory(tree): - name = read_wscript_variable(os.getcwd(), 'APPNAME') - command('./waf dist') - return os.path.abspath('%s-%s.tar.bz2' % (name, tree.version)), tree.git_commit - - -# @param s Target string: -# windows-{32,64} -# or ubuntu-version-{32,64} -# or debian-version-{32,64} -# or centos-version-{32,64} -# or osx-{32,64} -# or source -# @param debug True to build with debugging symbols (where possible) -def target_factory(s, debug, work): - target = None - if s.startswith('windows-'): - target = WindowsTarget(int(s.split('-')[1]), work) - elif s.startswith('ubuntu-') or s.startswith('debian-') or s.startswith('centos-'): - p = s.split('-') - if len(p) != 3: - raise Error("Bad Linux target name `%s'; must be something like ubuntu-12.04-32 (i.e. distro-version-bits)" % s) - target = ChrootTarget(p[0], p[1], int(p[2]), work) - elif s == 'raspbian': - target = ChrootTarget(s, None, None, work) - elif s == 'host': - try: - f = open('/etc/fedora-release', 'r') - l = f.readline().strip().split() - if command_and_read('uname -m').read().strip() == 'x86_64': - bits = 64 - else: - bits = 32 - target = HostTarget("fedora", l[2], bits, work) - except Exception as e: - raise Error("could not identify distribution for `host' target (%s)" % e) - elif s.startswith('osx-'): - target = OSXSingleTarget(int(s.split('-')[1]), work) - elif s == 'osx': - if globals.command == 'build': - target = OSXSingleTarget(64, work) - else: - target = OSXUniversalTarget(work) - elif s == 'source': - target = SourceTarget() - - if target is None: - raise Error("Bad target `%s'" % s) - - target.debug = debug - return target - - -# -# Tree -# - -class Tree(object): - """Description of a tree, which is a checkout of a project, - possibly built. This class is never exposed to cscripts. - Attributes: - name -- name of git repository (without the .git) - specifier -- git tag or revision to use - target --- target object that we are using - version --- version from the wscript (if one is present) - git_commit -- git revision that is actually being used - built --- true if the tree has been built yet in this run - """ - - def __init__(self, name, specifier, target): - self.name = name - self.specifier = specifier - self.target = target - self.version = None - self.git_commit = None - self.built = False - - cwd = os.getcwd() - - flags = '' - redirect = '' - if globals.quiet: - flags = '-q' - redirect = '>/dev/null' - command('git clone %s %s/%s.git %s/src/%s' % (flags, config.get('git_prefix'), self.name, target.directory, self.name)) - os.chdir('%s/src/%s' % (target.directory, self.name)) - - spec = self.specifier - if spec is None: - spec = 'master' - - command('git checkout %s %s %s' % (flags, spec, redirect)) - self.git_commit = command_and_read('git rev-parse --short=7 HEAD').readline().strip() - command('git submodule init --quiet') - command('git submodule update --quiet') - - proj = '%s/src/%s' % (target.directory, self.name) - - self.cscript = {} - execfile('%s/cscript' % proj, self.cscript) - - if os.path.exists('%s/wscript' % proj): - v = read_wscript_variable(proj, "VERSION"); - if v is not None: - self.version = Version(v) - - os.chdir(cwd) - - def call(self, function, *args): - with TreeDirectory(self): - return self.cscript[function](self.target, *args) - - def build_dependencies(self): - if 'dependencies' in self.cscript: - for d in self.cscript['dependencies'](self.target): - log('Building dependency %s %s of %s' % (d[0], d[1], self.name)) - dep = globals.trees.get(d[0], d[1], self.target) - dep.build_dependencies() - - # Make the options to pass in from the option_defaults of the thing - # we are building and any options specified by the parent. - options = {} - if 'option_defaults' in dep.cscript: - options = dep.cscript['option_defaults']() - if len(d) > 2: - for k, v in d[2].iteritems(): - options[k] = v - - dep.build(options) - - def build(self, options=None): - if self.built: - return - - variables = copy.copy(self.target.variables) - - if len(inspect.getargspec(self.cscript['build']).args) == 2: - self.call('build', options) - else: - self.call('build') - - self.target.variables = variables - self.built = True - -# -# Command-line parser -# - -def main(): - - commands = { - "build": "build project", - "package": "package and build project", - "release": "release a project using its next version number (changing wscript and tagging)", - "pot": "build the project's .pot files", - "changelog": "generate a simple HTML changelog", - "manual": "build the project's manual", - "doxygen": "build the project's Doxygen documentation", - "latest": "print out the latest version", - "test": "run the project's unit tests", - "shell": "build the project then start a shell in its chroot", - "checkout": "check out the project", - "revision": "print the head git revision number" - } - - one_of = "Command is one of:\n" - summary = "" - for k, v in commands.iteritems(): - one_of += "\t%s\t%s\n" % (k, v) - summary += k + " " - - parser = argparse.ArgumentParser() - parser.add_argument('command', help=summary) - parser.add_argument('-p', '--project', help='project name') - parser.add_argument('--minor', help='minor version number bump', action='store_true') - parser.add_argument('--micro', help='micro version number bump', action='store_true') - parser.add_argument('--major', help='major version to return with latest', type=int) - parser.add_argument('-c', '--checkout', help='string to pass to git for checkout') - parser.add_argument('-o', '--output', help='output directory', default='.') - parser.add_argument('-q', '--quiet', help='be quiet', action='store_true') - parser.add_argument('-t', '--target', help='target') - parser.add_argument('-k', '--keep', help='keep working tree', action='store_true') - parser.add_argument('--debug', help='build with debugging symbols where possible', action='store_true') - parser.add_argument('-w', '--work', help='override default work directory') - parser.add_argument('-g', '--git-prefix', help='override configured git prefix') - args = parser.parse_args() - - # Override configured stuff - if args.git_prefix is not None: - config.set('git_prefix', args.git_prefix) - - if args.output.find(':') == -1: - # This isn't of the form host:path so make it absolute - args.output = os.path.abspath(args.output) + '/' - else: - if args.output[-1] != ':' and args.output[-1] != '/': - args.output += '/' - - # Now, args.output is 'host:', 'host:path/' or 'path/' - - if args.work is not None: - args.work = os.path.abspath(args.work) - - if args.project is None and args.command != 'shell': - raise Error('you must specify -p or --project') - - globals.quiet = args.quiet - globals.command = args.command - - if not globals.command in commands: - e = 'command must be one of:\n' + one_of - raise Error('command must be one of:\n%s' % one_of) - - if globals.command == 'build': - if args.target is None: - raise Error('you must specify -t or --target') - - target = target_factory(args.target, args.debug, args.work) - tree = globals.trees.get(args.project, args.checkout, target) - tree.build_dependencies() - tree.build() - if not args.keep: - target.cleanup() - - elif globals.command == 'package': - if args.target is None: - raise Error('you must specify -t or --target') - - target = target_factory(args.target, args.debug, args.work) - packages, git_commit = target.package(args.project, args.checkout) - if hasattr(packages, 'strip') or (not hasattr(packages, '__getitem__') and not hasattr(packages, '__iter__')): - packages = [packages] - - if target.platform == 'linux': - out = '%s%s-%s-%d' % (args.output, target.distro, target.version, target.bits) - try: - makedirs(out) - except: - pass - for p in packages: - copyfile(p, '%s/%s' % (out, os.path.basename(devel_to_git(git_commit, p)))) - else: - try: - makedirs(args.output) - except: - pass - for p in packages: - copyfile(p, '%s%s' % (args.output, os.path.basename(devel_to_git(git_commit, p)))) - - if not args.keep: - target.cleanup() - - elif globals.command == 'release': - if args.minor is False and args.micro is False: - raise Error('you must specify --minor or --micro') - - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - version = tree.version - version.to_release() - if args.minor: - version.bump_minor() - else: - version.bump_micro() - - set_version_in_wscript(version) - append_version_to_changelog(version) - append_version_to_debian_changelog(version) - - command('git commit -a -m "Bump version"') - command('git tag -m "v%s" v%s' % (version, version)) - - version.to_devel() - set_version_in_wscript(version) - command('git commit -a -m "Bump version"') - command('git push') - command('git push --tags') - - target.cleanup() - - elif globals.command == 'pot': - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - pots = tree.call('make_pot') - for p in pots: - copyfile(p, '%s%s' % (args.output, os.path.basename(p))) - - target.cleanup() - - elif globals.command == 'changelog': - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - with TreeDirectory(tree): - text = open('ChangeLog', 'r') - - html = tempfile.NamedTemporaryFile() - versions = 8 - - last = None - changes = [] - - while True: - l = text.readline() - if l == '': - break - - if len(l) > 0 and l[0] == "\t": - s = l.split() - if len(s) == 4 and s[1] == "Version" and s[3] == "released.": - v = Version(s[2]) - if v.micro == 0: - if last is not None and len(changes) > 0: - print >>html,"<h2>Changes between version %s and %s</h2>" % (s[2], last) - print >>html,"<ul>" - for c in changes: - print >>html,"<li>%s" % c - print >>html,"</ul>" - last = s[2] - changes = [] - versions -= 1 - if versions < 0: - break - else: - c = l.strip() - if len(c) > 0: - if c[0] == '*': - changes.append(c[2:]) - else: - changes[-1] += " " + c - - copyfile(html.file, '%schangelog.html' % args.output) - html.close() - target.cleanup() - - elif globals.command == 'manual': - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - outs = tree.call('make_manual') - for o in outs: - if os.path.isfile(o): - copyfile(o, '%s%s' % (args.output, os.path.basename(o))) - else: - copytree(o, '%s%s' % (args.output, os.path.basename(o))) - - target.cleanup() - - elif globals.command == 'doxygen': - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - dirs = tree.call('make_doxygen') - if hasattr(dirs, 'strip') or (not hasattr(dirs, '__getitem__') and not hasattr(dirs, '__iter__')): - dirs = [dirs] - - for d in dirs: - copytree(d, args.output) - - target.cleanup() - - elif globals.command == 'latest': - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - - with TreeDirectory(tree): - f = command_and_read('git log --tags --simplify-by-decoration --pretty="%d"') - latest = None - while latest is None: - t = f.readline() - m = re.compile(".*\((.*)\).*").match(t) - if m: - tags = m.group(1).split(', ') - for t in tags: - s = t.split() - if len(s) > 1: - t = s[1] - if len(t) > 0 and t[0] == 'v': - v = Version(t[1:]) - if args.major is None or v.major == args.major: - latest = v - - print latest - target.cleanup() - - elif globals.command == 'test': - if args.target is None: - raise Error('you must specify -t or --target') - - target = None - try: - target = target_factory(args.target, args.debug, args.work) - tree = globals.trees.get(args.project, args.checkout, target) - with TreeDirectory(tree): - target.test(tree) - except Error as e: - if target is not None: - target.cleanup() - raise - - if target is not None: - target.cleanup() - - elif globals.command == 'shell': - if args.target is None: - raise Error('you must specify -t or --target') - - target = target_factory(args.target, args.debug, args.work) - target.command('bash') - - elif globals.command == 'revision': - - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - with TreeDirectory(tree): - print command_and_read('git rev-parse HEAD').readline().strip()[:7] - target.cleanup() - - elif globals.command == 'checkout': - - if args.output is None: - raise Error('you must specify -o or --output') - - target = SourceTarget() - tree = globals.trees.get(args.project, args.checkout, target) - with TreeDirectory(tree): - shutil.copytree('.', args.output) - target.cleanup() - - else: - raise Error('invalid command %s' % globals.command) - -try: - main() -except Error as e: - print >>sys.stderr,'cdist: %s' % str(e) - sys.exit(1) diff --git a/cdist/__init__.py b/cdist/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cdist/__init__.py diff --git a/cdist/cmd_build.py b/cdist/cmd_build.py new file mode 100644 index 0000000..200b8e4 --- /dev/null +++ b/cdist/cmd_build.py @@ -0,0 +1,38 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from util import Error +from target import factory as target_factory +import globals + +class CmdBuild(object): + def __init__(self): + self.name = 'build' + self.help = 'build project' + + def run(self, args): + if args.target is None: + raise Error('you must specify -t or --target') + + target = target_factory(args.target, args.debug, args.work) + tree = globals.trees.get(args.project, args.checkout, target) + tree.build_dependencies() + tree.build() + if not args.keep: + target.cleanup() + + diff --git a/cdist/cmd_checkout.py b/cdist/cmd_checkout.py new file mode 100644 index 0000000..77f02a3 --- /dev/null +++ b/cdist/cmd_checkout.py @@ -0,0 +1,42 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import shutil + +from target import SourceTarget +from tree_directory import TreeDirectory +import globals +from util import * + +class CmdCheckout(object): + def __init__(self): + self.name = 'checkout' + self.help = 'check out the project' + + def run(self, args): + if args.output is None: + raise Error('you must specify -o or --output') + + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + with TreeDirectory(tree): + try: + os.makedirs(args.output) + except: + pass + copytree('.', args.output) + target.cleanup() diff --git a/cdist/cmd_doxygen.py b/cdist/cmd_doxygen.py new file mode 100644 index 0000000..dc15feb --- /dev/null +++ b/cdist/cmd_doxygen.py @@ -0,0 +1,34 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from target import SourceTarget +import globals +from util import * + +class CmdDoxygen(object): + def __init__(self): + self.name = 'doxygen' + self.help = "build the project's Doxygen documentation" + + def run(self, args): + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + + for d in maybe_single_to_list(tree.call('make_doxygen')): + copytree(d, args.output) + + target.cleanup() diff --git a/cdist/cmd_latest.py b/cdist/cmd_latest.py new file mode 100644 index 0000000..1131637 --- /dev/null +++ b/cdist/cmd_latest.py @@ -0,0 +1,57 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import re +from target import SourceTarget +from tree_directory import TreeDirectory +import globals +from util import * +from version import Version + +class CmdLatest(object): + def __init__(self): + self.name = 'latest' + self.help = 'print out the latest version' + + def run(self, args): + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + + with TreeDirectory(tree): + f = command_and_read('git log --tags --simplify-by-decoration --pretty="%d"') + latest = None + while latest is None: + t = f.readline() + if t == '': + break + m = re.compile(".*\((.*)\).*").match(t) + if m: + tags = m.group(1).split(', ') + for t in tags: + s = t.split() + if len(s) > 1: + t = s[1] + if len(t) > 0 and t[0] == 'v': + v = Version(t[1:]) + if args.major is None or v.major == args.major: + latest = v + + print latest + target.cleanup() + + # For tests + return latest diff --git a/cdist/cmd_manual.py b/cdist/cmd_manual.py new file mode 100644 index 0000000..3e15092 --- /dev/null +++ b/cdist/cmd_manual.py @@ -0,0 +1,36 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from target import SourceTarget +from util import * + +class CmdManual(object): + def __init__(self): + self.name = 'manual' + self.help = "build the project's manual" + + def run(self, args): + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + + for o in maybe_single_to_list(tree.call('make_manual')): + if os.path.isfile(o): + copyfile(o, os.path.join(args.output, os.path.basename(o))) + else: + copytree(o, os.path.join(args.output, os.path.basename(o))) + + target.cleanup() diff --git a/cdist/cmd_package.py b/cdist/cmd_package.py new file mode 100644 index 0000000..166ace3 --- /dev/null +++ b/cdist/cmd_package.py @@ -0,0 +1,54 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os + +from target import factory as target_factory +from util import copyfile, devel_to_git + +class CmdPackage(object): + def __init__(self): + self.name = 'package' + self.help = "build and package project" + + def run(self, args): + if args.target is None: + raise Error('you must specify -t or --target') + + target = target_factory(args.target, args.debug, args.work) + packages, git_commit = target.package(args.project, args.checkout) + if hasattr(packages, 'strip') or (not hasattr(packages, '__getitem__') and not hasattr(packages, '__iter__')): + packages = [packages] + + if target.platform == 'linux': + out = os.path.join(args.output, '%s-%s-%d' % (target.distro, target.version, target.bits)) + try: + makedirs(out) + except: + pass + for p in packages: + copyfile(p, os.path.join(out, os.path.basename(devel_to_git(git_commit, p)))) + else: + try: + makedirs(args.output) + except: + pass + for p in packages: + copyfile(p, os.path.join(args.output, os.path.basename(devel_to_git(git_commit, p)))) + + if not args.keep: + target.cleanup() diff --git a/cdist/cmd_pot.py b/cdist/cmd_pot.py new file mode 100644 index 0000000..5a64e5c --- /dev/null +++ b/cdist/cmd_pot.py @@ -0,0 +1,14 @@ +class CmdPot(object): + def __init__(self): + self.name = 'pot' + self.help = "build the project's .pot files" + + def run(self, args): + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + + pots = tree.call('make_pot') + for p in pots: + copyfile(p, '%s%s' % (args.output, os.path.basename(p))) + + target.cleanup() diff --git a/cdist/cmd_release.py b/cdist/cmd_release.py new file mode 100644 index 0000000..6400121 --- /dev/null +++ b/cdist/cmd_release.py @@ -0,0 +1,33 @@ +class CmdRelease(object): + def __init__(self): + self.name = 'release' + self.help = "release a project using its next version number (changing wscript and tagging)" + + def run(self, args): + if args.minor is False and args.micro is False: + raise Error('you must specify --minor or --micro') + + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + + version = tree.version + version.to_release() + if args.minor: + version.bump_minor() + else: + version.bump_micro() + + set_version_in_wscript(version) + append_version_to_changelog(version) + append_version_to_debian_changelog(version) + + command('git commit -a -m "Bump version"') + command('git tag -m "v%s" v%s' % (version, version)) + + version.to_devel() + set_version_in_wscript(version) + command('git commit -a -m "Bump version"') + command('git push') + command('git push --tags') + + target.cleanup() diff --git a/cdist/cmd_revision.py b/cdist/cmd_revision.py new file mode 100644 index 0000000..ea5f2ea --- /dev/null +++ b/cdist/cmd_revision.py @@ -0,0 +1,11 @@ +class CmdRevision(object): + def __init__(self): + self.name = 'revision' + self.help = 'print the head git revision number' + + def run(self, args): + target = SourceTarget() + tree = globals.trees.get(args.project, args.checkout, target) + with TreeDirectory(tree): + print command_and_read('git rev-parse HEAD').readline().strip()[:7] + target.cleanup() diff --git a/cdist/cmd_shell.py b/cdist/cmd_shell.py new file mode 100644 index 0000000..c42bd36 --- /dev/null +++ b/cdist/cmd_shell.py @@ -0,0 +1,12 @@ + +class CmdShell(object): + def __init__(self): + self.name = 'shell' + self.help = 'build the project then start a shell in its chroot' + + def run(self, args): + if args.target is None: + raise Error('you must specify -t or --target') + + target = target_factory(args.target, args.debug, args.work) + target.command('bash') diff --git a/cdist/cmd_test.py b/cdist/cmd_test.py new file mode 100644 index 0000000..2d75a3a --- /dev/null +++ b/cdist/cmd_test.py @@ -0,0 +1,23 @@ +class CmdTest(object): + def __init__(self): + self.name = 'test' + self.help = "run the project's unit tests" + + def run(self, args): + if args.target is None: + raise Error('you must specify -t or --target') + + target = None + try: + target = target_factory(args.target, args.debug, args.work) + tree = globals.trees.get(args.project, args.checkout, target) + with TreeDirectory(tree): + target.test(tree) + except Error as e: + if target is not None: + target.cleanup() + raise + + if target is not None: + target.cleanup() + diff --git a/cdist/command_line.py b/cdist/command_line.py new file mode 100644 index 0000000..585801e --- /dev/null +++ b/cdist/command_line.py @@ -0,0 +1,105 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import argparse +import os +import sys + +from util import Error +import globals +import cmd_build +import cmd_package +import cmd_release +import cmd_pot +import cmd_manual +import cmd_doxygen +import cmd_latest +import cmd_test +import cmd_shell +import cmd_checkout +import cmd_revision + +def main(): + try: + commands = [ + cmd_build.CmdBuild(), + cmd_package.CmdPackage(), + cmd_release.CmdRelease(), + cmd_pot.CmdPot(), + cmd_manual.CmdManual(), + cmd_doxygen.CmdDoxygen(), + cmd_latest.CmdLatest(), + cmd_test.CmdTest(), + cmd_shell.CmdShell(), + cmd_checkout.CmdCheckout(), + cmd_revision.CmdRevision() + ] + + one_of = "Command is one of:\n" + summary = "" + for c in commands: + one_of += "\t%s\t%s\n" % (c.name, c.help) + summary += c.name + " " + + parser = argparse.ArgumentParser() + parser.add_argument('command', help=summary) + parser.add_argument('-p', '--project', help='project name') + parser.add_argument('--minor', help='minor version number bump', action='store_true') + parser.add_argument('--micro', help='micro version number bump', action='store_true') + parser.add_argument('--major', help='major version to return with latest', type=int) + parser.add_argument('-c', '--checkout', help='string to pass to git for checkout') + parser.add_argument('-o', '--output', help='output directory', default='.') + parser.add_argument('-q', '--quiet', help='be quiet', action='store_true') + parser.add_argument('-t', '--target', help='target') + parser.add_argument('-k', '--keep', help='keep working tree', action='store_true') + parser.add_argument('--debug', help='build with debugging symbols where possible', action='store_true') + parser.add_argument('-w', '--work', help='override default work directory') + parser.add_argument('-g', '--git-prefix', help='override configured git prefix') + args = parser.parse_args() + + # Override configured stuff + if args.git_prefix is not None: + config.set('git_prefix', args.git_prefix) + + if args.output.find(':') == -1: + # This isn't of the form host:path so make it absolute + args.output = os.path.abspath(args.output) + '/' + else: + if args.output[-1] != ':' and args.output[-1] != '/': + args.output += '/' + + # Now, args.output is 'host:', 'host:path/' or 'path/' + + if args.work is not None: + args.work = os.path.abspath(args.work) + + if args.project is None and args.command != 'shell': + raise Error('you must specify -p or --project') + + globals.quiet = args.quiet + globals.command = args.command + + for c in commands: + if c.name == globals.command: + c.run(args) + sys.exit(0) + + raise Error('command must be one of:\n%s' % one_of) + + except Error as e: + print >>sys.stderr,'cdist: %s' % str(e) + sys.exit(1) diff --git a/cdist/config.py b/cdist/config.py new file mode 100644 index 0000000..a746ac2 --- /dev/null +++ b/cdist/config.py @@ -0,0 +1,78 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os + +class Option(object): + def __init__(self, key, default=None): + self.key = key + self.value = default + + def offer(self, key, value): + if key == self.key: + self.value = value + +class BoolOption(object): + def __init__(self, key): + self.key = key + self.value = False + + def offer(self, key, value): + if key == self.key: + self.value = (value == 'yes' or value == '1' or value == 'true') + +class Config: + def __init__(self): + self.options = [ Option('linux_chroot_prefix'), + Option('windows_environment_prefix'), + Option('mingw_prefix'), + Option('git_prefix'), + Option('osx_build_host'), + Option('osx_environment_prefix'), + Option('osx_sdk_prefix'), + Option('osx_sdk'), + Option('parallel', 4), + Option('temporary_directory', '/var/tmp') ] + + try: + f = open('%s/.config/cdist' % os.path.expanduser('~'), 'r') + while True: + l = f.readline() + if l == '': + break + + if len(l) > 0 and l[0] == '#': + continue + + s = l.strip().split() + if len(s) == 2: + for k in self.options: + k.offer(s[0], s[1]) + except: + raise + + def get(self, k): + for o in self.options: + if o.key == k: + return o.value + + raise Error('Required setting %s not found' % k) + + def set(self, k, v): + for o in self.options: + o.offer(k, v) + diff --git a/cdist/globals.py b/cdist/globals.py new file mode 100644 index 0000000..ba873ed --- /dev/null +++ b/cdist/globals.py @@ -0,0 +1,25 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from trees import Trees +from config import Config + +quiet = False +command = None +trees = Trees() +config = Config() + diff --git a/cdist/target.py b/cdist/target.py new file mode 100644 index 0000000..07d5854 --- /dev/null +++ b/cdist/target.py @@ -0,0 +1,297 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import tempfile +import os + +import globals +from util import * + +class Target(object): + """ + platform -- platform string (e.g. 'windows', 'linux', 'osx') + directory -- directory to work in; if None we will use a temporary directory + Temporary directories will be removed after use; specified directories will not. + """ + def __init__(self, platform, directory=None): + self.platform = platform + self.parallel = int(globals.config.get('parallel')) + + # self.directory is the working directory + if directory is None: + self.directory = tempfile.mkdtemp('', 'tmp', globals.config.get('temporary_directory')) + self.rmdir = True + else: + self.directory = directory + self.rmdir = False + + # Environment variables that we will use when we call cscripts + self.variables = {} + self.debug = False + + def package(self, project, checkout): + tree = globals.trees.get(project, checkout, self) + tree.build_dependencies() + tree.build(tree) + return tree.call('package', tree.version), tree.git_commit + + def test(self, tree): + tree.build_dependencies() + tree.build() + return tree.call('test') + + def set(self, a, b): + self.variables[a] = b + + def unset(self, a): + del(self.variables[a]) + + def get(self, a): + return self.variables[a] + + def append_with_space(self, k, v): + if (not k in self.variables) or len(self.variables[k]) == 0: + self.variables[k] = '"%s"' % v + else: + e = self.variables[k] + if e[0] == '"' and e[-1] == '"': + self.variables[k] = '"%s %s"' % (e[1:-1], v) + else: + self.variables[k] = '"%s %s"' % (e, v) + + def variables_string(self, escaped_quotes=False): + e = '' + for k, v in self.variables.iteritems(): + if escaped_quotes: + v = v.replace('"', '\\"') + e += '%s=%s ' % (k, v) + return e + + def cleanup(self): + if self.rmdir: + rmtree(self.directory) + +# +# Windows +# + +class WindowsTarget(Target): + def __init__(self, bits, directory=None): + super(WindowsTarget, self).__init__('windows', directory) + self.bits = bits + + self.windows_prefix = '%s/%d' % (globals.config.get('windows_environment_prefix'), self.bits) + if not os.path.exists(self.windows_prefix): + raise Error('windows prefix %s does not exist' % self.windows_prefix) + + if self.bits == 32: + self.mingw_name = 'i686' + else: + self.mingw_name = 'x86_64' + + self.mingw_path = '%s/%d/bin' % (globals.config.get('mingw_prefix'), self.bits) + self.mingw_prefixes = ['/%s/%d' % (globals.config.get('mingw_prefix'), self.bits), + '%s/%d/%s-w64-mingw32' % (globals.config.get('mingw_prefix'), bits, self.mingw_name)] + + self.set('PKG_CONFIG_LIBDIR', '%s/lib/pkgconfig' % self.windows_prefix) + self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/bin/pkgconfig' % (self.directory, self.directory)) + self.set('PATH', '%s/bin:%s:%s' % (self.windows_prefix, self.mingw_path, os.environ['PATH'])) + self.set('CC', '%s-w64-mingw32-gcc' % self.mingw_name) + self.set('CXX', '%s-w64-mingw32-g++' % self.mingw_name) + self.set('LD', '%s-w64-mingw32-ld' % self.mingw_name) + self.set('RANLIB', '%s-w64-mingw32-ranlib' % self.mingw_name) + self.set('WINRC', '%s-w64-mingw32-windres' % self.mingw_name) + cxx = '-I%s/include -I%s/include' % (self.windows_prefix, self.directory) + link = '-L%s/lib -L%s/lib' % (self.windows_prefix, self.directory) + for p in self.mingw_prefixes: + cxx += ' -I%s/include' % p + link += ' -L%s/lib' % p + self.set('CXXFLAGS', '"%s"' % cxx) + self.set('CPPFLAGS', '') + self.set('LINKFLAGS', '"%s"' % link) + self.set('LDFLAGS', '"%s"' % link) + + def command(self, c): + log('host -> %s' % c) + command('%s %s' % (self.variables_string(), c)) + +class LinuxTarget(Target): + """Parent for Linux targets""" + def __init__(self, distro, version, bits, directory=None): + super(LinuxTarget, self).__init__('linux', directory) + self.distro = distro + self.version = version + self.bits = bits + + self.set('CXXFLAGS', '-I%s/include' % self.directory) + self.set('CPPFLAGS', '') + self.set('LINKFLAGS', '-L%s/lib' % self.directory) + self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:/usr/local/lib/pkgconfig' % self.directory) + self.set('PATH', '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin') + +class ChrootTarget(LinuxTarget): + """Build in a chroot""" + def __init__(self, distro, version, bits, directory=None): + super(ChrootTarget, self).__init__(distro, version, bits, directory) + # e.g. ubuntu-14.04-64 + if self.version is not None and self.bits is not None: + self.chroot = '%s-%s-%d' % (self.distro, self.version, self.bits) + else: + self.chroot = self.distro + # e.g. /home/carl/Environments/ubuntu-14.04-64 + self.chroot_prefix = '%s/%s' % (globals.config.get('linux_chroot_prefix'), self.chroot) + + def command(self, c): + command('%s schroot -c %s -p -- %s' % (self.variables_string(), self.chroot, c)) + + +class HostTarget(LinuxTarget): + """Build directly on the host""" + def __init__(self, distro, version, bits, directory=None): + super(HostTarget, self).__init__(distro, version, bits, directory) + + def command(self, c): + command('%s %s' % (self.variables_string(), c)) + +# +# OS X +# + +class OSXTarget(Target): + def __init__(self, directory=None): + super(OSXTarget, self).__init__('osx', directory) + self.sdk = globals.config.get('osx_sdk') + self.sdk_prefix = globals.config.get('osx_sdk_prefix') + + def command(self, c): + command('%s %s' % (self.variables_string(False), c)) + + +class OSXSingleTarget(OSXTarget): + def __init__(self, bits, directory=None): + super(OSXSingleTarget, self).__init__(directory) + self.bits = bits + + if bits == 32: + arch = 'i386' + else: + arch = 'x86_64' + + flags = '-isysroot %s/MacOSX%s.sdk -arch %s' % (self.sdk_prefix, self.sdk, arch) + enviro = '%s/%d' % (globals.config.get('osx_environment_prefix'), bits) + + # Environment variables + self.set('CFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags)) + self.set('CPPFLAGS', '') + self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (self.directory, enviro, flags)) + self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags)) + self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.directory, enviro, flags)) + self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/lib/pkgconfig:/usr/lib/pkgconfig' % (self.directory, enviro)) + self.set('PATH', '$PATH:/usr/bin:/sbin:/usr/local/bin:%s/bin' % enviro) + self.set('MACOSX_DEPLOYMENT_TARGET', globals.config.get('osx_sdk')) + + def package(self, project, checkout): + raise Error('cannot package non-universal OS X versions') + + +class OSXUniversalTarget(OSXTarget): + def __init__(self, directory=None): + super(OSXUniversalTarget, self).__init__(directory) + + def package(self, project, checkout): + + for b in [32, 64]: + target = OSXSingleTarget(b, os.path.join(self.directory, '%d' % b)) + tree = globals.trees.get(project, checkout, target) + tree.build_dependencies() + tree.build() + + tree = globals.trees.get(project, checkout, self) + with TreeDirectory(tree): + return tree.call('package', tree.version), tree.git_commit + +class SourceTarget(Target): + """Build a source .tar.bz2""" + def __init__(self): + super(SourceTarget, self).__init__('source') + + def command(self, c): + log('host -> %s' % c) + command('%s %s' % (self.variables_string(), c)) + + def cleanup(self): + rmtree(self.directory) + + def package(self, project, checkout): + tree = globals.trees.get(project, checkout, self) + with TreeDirectory(tree): + name = read_wscript_variable(os.getcwd(), 'APPNAME') + command('./waf dist') + return os.path.abspath('%s-%s.tar.bz2' % (name, tree.version)), tree.git_commit + +class TestTarget(Target): + def __init__(self): + super(TestTarget, self).__init__('test', os.path.realpath('test')) + +# @param s Target string: +# windows-{32,64} +# or ubuntu-version-{32,64} +# or debian-version-{32,64} +# or centos-version-{32,64} +# or osx-{32,64} +# or source +# @param debug True to build with debugging symbols (where possible) +def factory(s, debug, work): + target = None + if s.startswith('windows-'): + target = WindowsTarget(int(s.split('-')[1]), work) + elif s.startswith('ubuntu-') or s.startswith('debian-') or s.startswith('centos-'): + p = s.split('-') + if len(p) != 3: + raise Error("Bad Linux target name `%s'; must be something like ubuntu-12.04-32 (i.e. distro-version-bits)" % s) + target = ChrootTarget(p[0], p[1], int(p[2]), work) + elif s == 'raspbian': + target = ChrootTarget(s, None, None, work) + elif s == 'host': + try: + f = open('/etc/fedora-release', 'r') + l = f.readline().strip().split() + if command_and_read('uname -m').read().strip() == 'x86_64': + bits = 64 + else: + bits = 32 + target = HostTarget("fedora", l[2], bits, work) + except Exception as e: + raise Error("could not identify distribution for `host' target (%s)" % e) + elif s.startswith('osx-'): + target = OSXSingleTarget(int(s.split('-')[1]), work) + elif s == 'osx': + if globals.command == 'build': + target = OSXSingleTarget(64, work) + else: + target = OSXUniversalTarget(work) + elif s == 'source': + target = SourceTarget() + elif s == 'test': + target = TestTarget() + + if target is None: + raise Error("Bad target `%s'" % s) + + target.debug = debug + return target + diff --git a/cdist/tests/__init__.py b/cdist/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cdist/tests/__init__.py diff --git a/cdist/tests/prepare.py b/cdist/tests/prepare.py new file mode 100644 index 0000000..dcb304e --- /dev/null +++ b/cdist/tests/prepare.py @@ -0,0 +1,40 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import shutil +import os + +import cdist.util + +def project(): + try: + shutil.rmtree('test') + except: + pass + os.makedirs('test/project.git') + os.chdir('test/project.git') + cdist.util.command('git init .') + with open('cscript', 'w') as f: + print>>f,'import os' + print>>f,'def build(target, options):' + print>>f,' os.system("touch hello")' + print>>f,'def package(target, version):' + print>>f,' os.system("touch goodbye")' + print>>f,' return os.path.abspath("goodbye")' + cdist.util.command('git add .') + cdist.util.command('git commit -a -m "foo"') + os.chdir('../..') diff --git a/cdist/tests/test_cmd_build.py b/cdist/tests/test_cmd_build.py new file mode 100644 index 0000000..89e6d84 --- /dev/null +++ b/cdist/tests/test_cmd_build.py @@ -0,0 +1,25 @@ +import os +import shutil +from unittest import TestCase + +import cdist.cmd_build +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdBuild(TestCase): + def test(self): + + prepare.project() + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + cdist.globals.config.set('git_prefix', 'test') + cdist.cmd_build.CmdBuild().run(Args()) + + self.assertTrue(os.path.exists('test/src/project/hello')) diff --git a/cdist/tests/test_cmd_checkout.py b/cdist/tests/test_cmd_checkout.py new file mode 100644 index 0000000..ca16431 --- /dev/null +++ b/cdist/tests/test_cmd_checkout.py @@ -0,0 +1,43 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import shutil +from unittest import TestCase + +import cdist.cmd_checkout +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdCheckout(TestCase): + def test(self): + + prepare.project() + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + output = os.path.abspath('test') + cdist.globals.config.set('git_prefix', 'test') + cdist.cmd_checkout.CmdCheckout().run(Args()) + + self.assertTrue(os.path.exists('test/cscript')) diff --git a/cdist/tests/test_cmd_doxygen.py b/cdist/tests/test_cmd_doxygen.py new file mode 100644 index 0000000..b82b0c9 --- /dev/null +++ b/cdist/tests/test_cmd_doxygen.py @@ -0,0 +1,51 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import shutil +from unittest import TestCase + +import cdist.cmd_doxygen +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdDoxygen(TestCase): + def test(self): + + prepare.project() + os.chdir('test/project.git') + with open('cscript', 'a') as f: + print>>f,'import os' + print>>f,'def make_doxygen(target):' + print>>f,' os.system("touch doxy")' + print>>f,' return os.path.abspath("doxy")' + command('git commit -a -m "foo"') + os.chdir('../..') + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + output = os.path.abspath('test') + cdist.globals.config.set('git_prefix', 'test') + cdist.cmd_doxygen.CmdDoxygen().run(Args()) + + self.assertTrue(os.path.exists('test/doxy')) diff --git a/cdist/tests/test_cmd_latest.py b/cdist/tests/test_cmd_latest.py new file mode 100644 index 0000000..22c7575 --- /dev/null +++ b/cdist/tests/test_cmd_latest.py @@ -0,0 +1,45 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import shutil +from unittest import TestCase + +import cdist.cmd_latest +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdLatest(TestCase): + def test(self): + + prepare.project() + os.chdir('test/project.git') + command('git tag v0.0.1') + os.chdir('../..') + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + output = os.path.abspath('test') + major = 0 + cdist.globals.config.set('git_prefix', 'test') + self.assertEquals(str(cdist.cmd_latest.CmdLatest().run(Args())), '0.0.1') diff --git a/cdist/tests/test_cmd_manual.py b/cdist/tests/test_cmd_manual.py new file mode 100644 index 0000000..8f5c401 --- /dev/null +++ b/cdist/tests/test_cmd_manual.py @@ -0,0 +1,51 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import shutil +from unittest import TestCase + +import cdist.cmd_manual +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdManual(TestCase): + def test(self): + + prepare.project() + os.chdir('test/project.git') + with open('cscript', 'a') as f: + print>>f,'import os' + print>>f,'def make_manual(target):' + print>>f,' os.system("touch manu")' + print>>f,' return os.path.abspath("manu")' + command('git commit -a -m "foo"') + os.chdir('../..') + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + output = os.path.abspath('test') + cdist.globals.config.set('git_prefix', 'test') + cdist.cmd_manual.CmdManual().run(Args()) + + self.assertTrue(os.path.exists('test/manu')) diff --git a/cdist/tests/test_cmd_package.py b/cdist/tests/test_cmd_package.py new file mode 100644 index 0000000..34efa83 --- /dev/null +++ b/cdist/tests/test_cmd_package.py @@ -0,0 +1,44 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import shutil +from unittest import TestCase + +import cdist.cmd_package +import cdist.globals +from cdist.util import command +import prepare + +class TestCmdPackage(TestCase): + def test(self): + + prepare.project() + + class Args: + target = 'test' + debug = False + work = 'test' + project = 'project' + checkout = None + keep = False + output = 'test' + cdist.globals.config.set('git_prefix', 'test') + cdist.cmd_package.CmdPackage().run(Args()) + + self.assertTrue(os.path.exists('test/src/project/hello')) + self.assertTrue(os.path.exists('test/goodbye')) diff --git a/cdist/tree.py b/cdist/tree.py new file mode 100644 index 0000000..11bc4dc --- /dev/null +++ b/cdist/tree.py @@ -0,0 +1,112 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import copy +import inspect + +import globals +from util import * +from version import Version +from tree_directory import TreeDirectory + +class Tree(object): + """Description of a tree, which is a checkout of a project, + possibly built. This class is never exposed to cscripts. + Attributes: + name -- name of git repository (without the .git) + specifier -- git tag or revision to use + target --- target object that we are using + version --- version from the wscript (if one is present) + git_commit -- git revision that is actually being used + built --- true if the tree has been built yet in this run + """ + + def __init__(self, name, specifier, target): + self.name = name + self.specifier = specifier + self.target = target + self.version = None + self.git_commit = None + self.built = False + + cwd = os.getcwd() + + flags = '' + redirect = '' + if globals.quiet: + flags = '-q' + redirect = '>/dev/null' + command('git clone %s %s/%s.git %s/src/%s' % (flags, globals.config.get('git_prefix'), self.name, target.directory, self.name)) + chdir('%s/src/%s' % (target.directory, self.name)) + + spec = self.specifier + if spec is None: + spec = 'master' + + command('git checkout %s %s %s' % (flags, spec, redirect)) + self.git_commit = command_and_read('git rev-parse --short=7 HEAD').readline().strip() + command('git submodule init --quiet') + command('git submodule update --quiet') + + proj = '%s/src/%s' % (target.directory, self.name) + + self.cscript = {} + execfile('%s/cscript' % proj, self.cscript) + + if os.path.exists('%s/wscript' % proj): + v = read_wscript_variable(proj, "VERSION"); + if v is not None: + self.version = Version(v) + + chdir(cwd) + + def call(self, function, *args): + with TreeDirectory(self): + return self.cscript[function](self.target, *args) + + def build_dependencies(self): + if 'dependencies' in self.cscript: + for d in self.cscript['dependencies'](self.target): + log('Building dependency %s %s of %s' % (d[0], d[1], self.name)) + dep = globals.trees.get(d[0], d[1], self.target) + dep.build_dependencies() + + # Make the options to pass in from the option_defaults of the thing + # we are building and any options specified by the parent. + options = {} + if 'option_defaults' in dep.cscript: + options = dep.cscript['option_defaults']() + if len(d) > 2: + for k, v in d[2].iteritems(): + options[k] = v + + dep.build(options) + + def build(self, options=None): + if self.built: + return + + variables = copy.copy(self.target.variables) + + if len(inspect.getargspec(self.cscript['build']).args) == 2: + self.call('build', options) + else: + self.call('build') + + self.target.variables = variables + self.built = True diff --git a/cdist/tree_directory.py b/cdist/tree_directory.py new file mode 100644 index 0000000..668845a --- /dev/null +++ b/cdist/tree_directory.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os + +from util import chdir + +class TreeDirectory: + def __init__(self, tree): + self.tree = tree + def __enter__(self): + self.cwd = os.getcwd() + chdir('%s/src/%s' % (self.tree.target.directory, self.tree.name)) + def __exit__(self, type, value, traceback): + chdir(self.cwd) diff --git a/cdist/trees.py b/cdist/trees.py new file mode 100644 index 0000000..84b3c73 --- /dev/null +++ b/cdist/trees.py @@ -0,0 +1,38 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +from tree import Tree + +class Trees: + """ + Store for Tree objects which re-uses already-created objects + and checks for requests for different versions of the same thing. + """ + + def __init__(self): + self.trees = [] + + def get(self, name, specifier, target): + for t in self.trees: + if t.name == name and t.specifier == specifier and t.target == target: + return t + elif t.name == name and t.specifier != specifier: + raise Error('conflicting versions of %s requested (%s and %s)' % (name, specifier, t.specifier)) + + nt = Tree(name, specifier, target) + self.trees.append(nt) + return nt diff --git a/cdist/util.py b/cdist/util.py new file mode 100644 index 0000000..082459c --- /dev/null +++ b/cdist/util.py @@ -0,0 +1,148 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import subprocess +import shutil + +class Error(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return self.value + def __repr__(self): + return str(self) + +def scp_escape(n): + s = n.split(':') + assert(len(s) == 1 or len(s) == 2) + if len(s) == 2: + return '%s:"\'%s\'"' % (s[0], s[1]) + else: + return '\"%s\"' % s[0] + +def copytree(a, b): + log('copy %s -> %s' % (scp_escape(b), scp_escape(b))) + command('scp -r %s %s' % (scp_escape(a), scp_escape(b))) + +def copyfile(a, b): + log('copy %s -> %s' % (scp_escape(a), scp_escape(b))) + command('scp %s %s' % (scp_escape(a), scp_escape(b))) + +def makedirs(d): + if d.find(':') == -1: + os.makedirs(d) + else: + s = d.split(':') + command('ssh %s -- mkdir -p %s' % (s[0], s[1])) + +def rmdir(a): + log('remove %s' % a) + os.rmdir(a) + +def rmtree(a): + log('remove %s' % a) + shutil.rmtree(a, ignore_errors=True) + +def chdir(a): + log('chdir %s' % a) + os.chdir(a) + +def command(c): + log(c) + r = os.system(c) + if (r >> 8): + raise Error('command %s failed' % c) + +def command_and_read(c): + log(c) + p = subprocess.Popen(c.split(), stdout=subprocess.PIPE) + f = os.fdopen(os.dup(p.stdout.fileno())) + return f + +def read_wscript_variable(directory, variable): + f = open('%s/wscript' % directory, 'r') + while True: + l = f.readline() + if l == '': + break + + s = l.split() + if len(s) == 3 and s[0] == variable: + f.close() + return s[2][1:-1] + + f.close() + return None + +def set_version_in_wscript(version): + f = open('wscript', 'rw') + o = open('wscript.tmp', 'w') + while True: + l = f.readline() + if l == '': + break + + s = l.split() + if len(s) == 3 and s[0] == "VERSION": + print "Writing %s" % version + print >>o,"VERSION = '%s'" % version + else: + print >>o,l, + f.close() + o.close() + + os.rename('wscript.tmp', 'wscript') + +def append_version_to_changelog(version): + try: + f = open('ChangeLog', 'r') + except: + log('Could not open ChangeLog') + return + + c = f.read() + f.close() + + f = open('ChangeLog', 'w') + now = datetime.datetime.now() + f.write('%d-%02d-%02d Carl Hetherington <cth@carlh.net>\n\n\t* Version %s released.\n\n' % (now.year, now.month, now.day, version)) + f.write(c) + +def append_version_to_debian_changelog(version): + if not os.path.exists('debian'): + log('Could not find debian directory') + return + + command('dch -b -v %s-1 "New upstream release."' % version) + +def devel_to_git(git_commit, filename): + if git_commit is not None: + filename = filename.replace('devel', '-%s' % git_commit) + return filename + +def maybe_single_to_list(s): + if hasattr(s, 'strip') or (not hasattr(s, '__getitem__') and not hasattr(s, '__iter__')): + return [s] + return s + +import globals + +def log(m): + if not globals.quiet: + print '\x1b[33m* %s\x1b[0m' % m + diff --git a/cdist/version.py b/cdist/version.py new file mode 100644 index 0000000..633f2c7 --- /dev/null +++ b/cdist/version.py @@ -0,0 +1,60 @@ +# +# Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net> +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +class Version: + def __init__(self, s): + self.devel = False + + if s.startswith("'"): + s = s[1:] + if s.endswith("'"): + s = s[0:-1] + + if s.endswith('devel'): + s = s[0:-5] + self.devel = True + + if s.endswith('pre'): + s = s[0:-3] + + p = s.split('.') + self.major = int(p[0]) + self.minor = int(p[1]) + if len(p) == 3: + self.micro = int(p[2]) + else: + self.micro = 0 + + def bump_minor(self): + self.minor += 1 + self.micro = 0 + + def bump_micro(self): + self.micro += 1 + + def to_devel(self): + self.devel = True + + def to_release(self): + self.devel = False + + def __str__(self): + s = '%d.%d.%d' % (self.major, self.minor, self.micro) + if self.devel: + s += 'devel' + + return s diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ebe4f99 --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from setuptools import setup + +setup(name='cdist', + version='0.1', + description='software build and distribution tool', + url='http://carlh.net/cdist', + author='Carl Hetherington', + author_email='cth@carlh.net', + license='GPL', + packages=['cdist'], + entry_points={'console_scripts': ['cdist=cdist.command_line:main'],}, + test_suite='nose.collector', + tests_require=['nose'], + zip_safe=False) |
