#!/usr/bin/python
-# Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+# Copyright (C) 2012-2014 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
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-# Configuration
-#
-
-# Directory to build things in within chroots
-DIR_IN_CHROOT = '/home/carl'
-# Prefix of chroots in the filesystem
-CHROOT_PREFIX = '/home/carl/Environments'
-# Prefix of windows environments
-WINDOWS_ENVIRONMENT_PREFIX = '/home/carl/Environments/windows'
-# Git prefix
-GIT_DIR = 'ssh://houllier/home/carl/git'
-OSX_BUILD_HOST = 'carl@192.168.1.202'
-DIR_ON_HOST = '/Users/carl/cdist'
-OSX_ENVIRONMENT_PREFIX = '/Users/carl/Environments/osx'
-OSX_SDK_PREFIX = '/Users/carl/SDK'
-
import os
import sys
import shutil
import datetime
import subprocess
import re
+import copy
+import inspect
+
+TEMPORARY_DIRECTORY = '/tmp'
+
+class Globals:
+ quiet = False
+ command = None
+
+globals = Globals()
+
+class Error(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return self.value
+ def __repr__(self):
+ return str(self)
+
+#
+# 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)
+
+config = Config()
#
# Utility bits
#
def log(m):
- if not args.quiet:
+ if not globals.quiet:
print '\x1b[33m* %s\x1b[0m' % m
-def error(e):
- print '\x1b[31mError: %s\x1b[0m' % e
- sys.exit(1)
+def scp_escape(n):
+ s = n.split(':')
+ assert(len(s) == 1 or len(s) == 2)
+ if len(s) == 2:
+ s[1] = '"\'%s\'"' % s[1]
+ return '%s:%s' % (s[0], s[1])
+ else:
+ return n
def copytree(a, b):
log('copy %s -> %s' % (a, b))
- shutil.copytree(a, b)
+ os.system('scp -r %s %s' % (scp_escape(a), scp_escape(b)))
def copyfile(a, b):
log('copy %s -> %s' % (a, b))
- shutil.copyfile(a, b)
+ os.system('scp %s %s' % (scp_escape(a), scp_escape(b)))
def rmdir(a):
log('remove %s' % a)
log(c)
r = os.system(c)
if (r >> 8) and not can_fail:
- error('command %s failed' % c)
+ raise Error('command %s failed' % c)
def command_and_read(c):
log(c)
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
#
# Version
class Version:
def __init__(self, s):
- self.pre = False
- self.beta = None
+ 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]
- self.pre = True
-
- b = s.find("beta")
- if b != -1:
- self.beta = int(s[b+4:])
- s = s[0:b]
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(self):
+ def bump_minor(self):
self.minor += 1
- self.pre = False
- self.beta = None
+ self.micro = 0
- def to_pre(self):
- self.pre = True
- self.beta = None
+ def bump_micro(self):
+ self.micro += 1
- def bump_and_to_pre(self):
- self.bump()
- self.pre = True
- self.beta = None
+ def to_devel(self):
+ self.devel = True
def to_release(self):
- self.pre = False
- self.beta = None
-
- def bump_beta(self):
- if self.pre:
- self.pre = False
- self.beta = 1
- elif self.beta is not None:
- self.beta += 1
- elif self.beta is None:
- self.beta = 1
+ self.devel = False
def __str__(self):
- s = '%d.%02d' % (self.major, self.minor)
- if self.beta is not None:
- s += 'beta%d' % self.beta
- elif self.pre:
- s += 'pre'
+ s = '%d.%d.%d' % (self.major, self.minor, self.micro)
+ if self.devel:
+ s += 'devel'
return s
-
#
-# Environment
+# Targets
#
-class Environment(object):
- def __init__(self):
+class Target(object):
+ # @param 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'))
+
+ 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 build_dependencies(self, project):
+ cwd = os.getcwd()
+ if 'dependencies' in project.cscript:
+ for d in project.cscript['dependencies'](self):
+ log('Building dependency %s %s of %s' % (d[0], d[1], project.name))
+ dep = Project(d[0], '.', d[1])
+ dep.checkout(self)
+ self.build_dependencies(dep)
+
+ # 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
+
+ self.build(dep, options)
+
+ os.chdir(cwd)
+
+ def build(self, project, options=None):
+ variables = copy.copy(self.variables)
+ if len(inspect.getargspec(project.cscript['build']).args) == 2:
+ project.cscript['build'](self, options)
+ else:
+ project.cscript['build'](self)
+ self.variables = variables
+
+ def package(self, project):
+ project.checkout(self)
+ self.build_dependencies(project)
+ self.build(project)
+ return project.cscript['package'](self, project.version)
+
+ def test(self, project):
+ project.checkout(self)
+ self.build_dependencies(project)
+ self.build(project)
+ project.cscript['test'](self)
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:
+ self.variables[k] = v
+ else:
+ self.variables[k] = '%s %s' % (self.variables[k], v)
+
def variables_string(self, escaped_quotes=False):
e = ''
for k, v in self.variables.iteritems():
e += '%s=%s ' % (k, v)
return e
- def work_dir_cdist(self, sub):
- assert(false)
-
- def work_dir_cscript(self):
- assert(false)
-
- def build_dependencies(self, target, project):
- cwd = os.getcwd()
- if 'dependencies' in project.cscript:
- for d in project.cscript['dependencies'](target):
- dep = Project(d[0], '.', d[1])
- dep.checkout(self)
- self.build(target, dep)
- os.chdir(cwd)
+ def cleanup(self):
+ if self.rmdir:
+ rmtree(self.directory)
- def build(self, target, project):
- project.cscript['build'](self, target)
+#
+# Windows
+#
- def package(self, target, project):
- project.checkout(self)
- if target.platform != 'source':
- self.build_dependencies(target, project)
- if target.platform == 'source':
- command('./waf dist')
- if project.directory != '.':
- return os.path.abspath('%s-%s.tar.bz2' % (project.directory, project.version))
- return os.path.abspath('%s-%s.tar.bz2' % (project.name, project.version))
+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:
- project.cscript['build'](self, target)
- return project.cscript['package'](self, target, project.version)
+ self.mingw_name = 'x86_64'
+
+ 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, 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)
- def cleanup(self):
- pass
+ def command(self, c):
+ log('host -> %s' % c)
+ command('%s %s' % (self.variables_string(), c))
#
-# ChrootEnvironment
+# Linux
#
-class ChrootEnvironment(Environment):
- def __init__(self, chroot):
- super(ChrootEnvironment, self).__init__()
- self.chroot = chroot
- self.dir_in_chroot = DIR_IN_CHROOT
- self.chroot_dir = CHROOT_PREFIX
+class LinuxTarget(Target):
+ def __init__(self, distro, version, bits, directory=None):
+ super(LinuxTarget, self).__init__('linux', directory)
+ self.distro = distro
+ self.version = version
+ self.bits = bits
+ # e.g. ubuntu-14.04-64
+ self.chroot = '%s-%s-%d' % (self.distro, self.version, self.bits)
+ # e.g. /home/carl/Environments/ubuntu-14.04-64
+ self.chroot_prefix = '%s/%s' % (config.get('linux_chroot_prefix'), self.chroot)
+
+ 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', '%s:/usr/local/bin' % (os.environ['PATH']))
- # ChrootEnvironments work in dir_in_chroot, and clear
- # it out before use
- for g in glob.glob('%s/*' % self.work_dir_cdist()):
- rmtree(g)
-
- # Environment variables
- self.set('CXXFLAGS', '-I%s/include' % self.work_dir_cscript())
- self.set('LINKFLAGS', '-L%s/lib' % self.work_dir_cscript())
- self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig' % self.work_dir_cscript())
+ def command(self, c):
+ command('%s schroot -c %s -p -- %s' % (self.variables_string(), self.chroot, c))
- def work_dir_cdist(self):
- return '%s/%s%s' % (self.chroot_dir, self.chroot, self.dir_in_chroot)
+#
+# OS X
+#
- def work_dir_cscript(self):
- return self.dir_in_chroot
+class OSXTarget(Target):
+ def __init__(self, directory=None):
+ super(OSXTarget, self).__init__('osx', directory)
def command(self, c):
- # Work out the cwd for the chrooted command
- cwd = os.getcwd()
- prefix = '%s/%s' % (self.chroot_dir, self.chroot)
- assert(cwd.startswith(prefix))
- cwd = cwd[len(prefix):]
-
- log('schroot [%s] -> %s' % (cwd, c))
- command('%s schroot -c %s -d %s -p -- %s' % (self.variables_string(), self.chroot, cwd, c))
+ command('%s %s' % (self.variables_string(False), c))
-#
-# RemoteEnvironment
-#
-
-class RemoteEnvironment(Environment):
- def __init__(self, host):
- super(RemoteEnvironment, self).__init__()
- self.host = host
- self.dir_on_host = DIR_ON_HOST
- self.host_mount_dir = tempfile.mkdtemp()
- self.osx_sdk = '10.6'
+class OSXSingleTarget(OSXTarget):
+ def __init__(self, bits, directory=None):
+ super(OSXSingleTarget, self).__init__(directory)
+ self.bits = bits
- # Mount the remote host on host_mount_dir
- command('sshfs %s:%s %s' % (self.host, self.dir_on_host, self.host_mount_dir))
- for g in glob.glob('%s/*' % self.host_mount_dir):
- rmtree(g)
+ if bits == 32:
+ arch = 'i386'
+ else:
+ arch = 'x86_64'
- sysroot = '-isysroot %s/MacOSX%s.sdk' % (OSX_SDK_PREFIX, self.osx_sdk)
- enviro = '%s/%s' % (OSX_ENVIRONMENT_PREFIX, self.osx_sdk)
+ flags = '-isysroot %s/MacOSX%s.sdk -arch %s' % (config.get('osx_sdk_prefix'), config.get('osx_sdk'), arch)
+ enviro = '%s/%d' % (config.get('osx_environment_prefix'), bits)
# Environment variables
- self.set('CCFLAGS', '"-I%s/include -I%s/include %s"' % (self.dir_on_host, enviro, sysroot))
- self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (self.dir_on_host, enviro, sysroot))
- self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.dir_on_host, enviro, sysroot))
- self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.dir_on_host, enviro, sysroot))
- self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/lib/pkgconfig' % (self.dir_on_host, enviro))
+ 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', self.osx_sdk)
-
- def work_dir_cdist(self):
- return self.host_mount_dir
+ self.set('MACOSX_DEPLOYMENT_TARGET', config.get('osx_sdk'))
- def work_dir_cscript(self):
- return self.dir_on_host
-
- def command(self, c):
- # Work out the cwd for the chrooted command
- cwd = os.getcwd()
- assert(cwd.startswith(self.host_mount_dir))
- cwd = cwd[len(self.host_mount_dir):]
+ def package(self, project):
+ raise Error('cannot package non-universal OS X versions')
- log('ssh [%s] -> %s' % (cwd, c))
- command('ssh %s -- "cd %s%s; %s %s"' % (self.host, self.dir_on_host, cwd, self.variables_string(True), c))
- def cleanup(self):
- os.chdir('/')
- command('fusermount -u %s' % self.host_mount_dir)
- rmdir(self.host_mount_dir)
+class OSXUniversalTarget(OSXTarget):
+ def __init__(self, directory=None):
+ super(OSXUniversalTarget, self).__init__(directory)
+ self.parts = []
+ self.parts.append(OSXSingleTarget(32, os.path.join(self.directory, '32')))
+ self.parts.append(OSXSingleTarget(64, os.path.join(self.directory, '64')))
+
+ def package(self, project):
+ for p in self.parts:
+ project.checkout(p)
+ p.build_dependencies(project)
+ p.build(project)
+
+ return project.cscript['package'](self, project.version)
+
#
-# HostEnvironment
+# Source
#
-class HostEnvironment(Environment):
- def __init__(self, directory=None):
- super(HostEnvironment, self).__init__()
- if directory is None:
- self.directory = tempfile.mkdtemp()
- self.rmdir = True
- else:
- self.directory = directory
- self.rmdir = False
-
- def work_dir_cdist(self):
- return self.directory
-
- def work_dir_cscript(self):
- return self.directory
+class SourceTarget(Target):
+ 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):
- if self.rmdir:
- rmtree(self.directory)
+ rmtree(self.directory)
+ def package(self, project):
+ project.checkout(self)
+ name = read_wscript_variable(os.getcwd(), 'APPNAME')
+ command('./waf dist')
+ return os.path.abspath('%s-%s.tar.bz2' % (name, project.version))
+
+
+# @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:
+ print >>sys.stderr,"Bad Linux target name `%s'; must be something like ubuntu-12.04-32 (i.e. distro-version-bits)" % s
+ sys.exit(1)
+ target = LinuxTarget(p[0], p[1], int(p[2]), work)
+ 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()
-def prepare_for_windows(env, bits):
- env.windows_prefix = '%s/%d' % (WINDOWS_ENVIRONMENT_PREFIX, bits)
- if not os.path.exists(env.windows_prefix):
- error('windows prefix %s does not exist' % env.windows_prefix)
+ if target is not None:
+ target.debug = debug
- if bits == 32:
- mingw_name = 'i686'
- else:
- mingw_name = 'x86_64'
-
- mingw_path = '/mingw/%d/bin' % bits
- mingw_prefixes = ['/mingw/%d' % bits, '/mingw/%d/%s-w64-mingw32' % (bits, mingw_name)]
-
- env.set('PKG_CONFIG_LIBDIR', '%s/lib/pkgconfig' % env.windows_prefix)
- env.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig' % env.work_dir_cscript())
- env.set('PATH', '%s/bin:%s:%s' % (env.windows_prefix, mingw_path, os.environ['PATH']))
- env.set('CC', '%s-w64-mingw32-gcc' % mingw_name)
- env.set('CXX', '%s-w64-mingw32-g++' % mingw_name)
- env.set('LD', '%s-w64-mingw32-ld' % mingw_name)
- env.set('RANLIB', '%s-w64-mingw32-ranlib' % mingw_name)
- env.set('WINRC', '%s-w64-mingw32-windres' % mingw_name)
- cxx = '-I%s/include -I%s/include' % (env.windows_prefix, env.work_dir_cscript())
- link = '-L%s/lib -L%s/lib' % (env.windows_prefix, env.work_dir_cscript())
- for p in mingw_prefixes:
- cxx += ' -I%s/include' % p
- link += ' -L%s/lib' % p
- env.set('CXXFLAGS', '"%s"' % cxx)
- env.set('LINKFLAGS', '"%s"' % link)
+ return target
-#
-# Target
-#
-
-class Target:
- def __init__(self, name):
- self.name = name
- if name.startswith('ubuntu-') or name.startswith('debian-'):
- self.platform = 'linux'
- self.version = name.split('-')[1]
- self.bits = int(name.split('-')[2])
- elif name.startswith('windows-'):
- self.platform = 'windows'
- self.bits = int(name.split('-')[1])
- elif name == 'osx':
- self.platform = 'osx'
- elif name == 'source':
- self.platform = 'source'
-
-def environment_for_target(target, directory):
- if target.platform == 'linux':
- return ChrootEnvironment(target.name)
- elif target.platform == 'windows':
- env = HostEnvironment(directory)
- prepare_for_windows(env, target.bits)
- return env
- elif target.platform == 'osx':
- env = RemoteEnvironment(OSX_BUILD_HOST)
- return env
- elif target.platform == 'source':
- return HostEnvironment()
-
- return None
-
#
# Project
#
def __init__(self, name, directory, specifier=None):
self.name = name
self.directory = directory
- self.git_dir = GIT_DIR
self.version = None
self.specifier = specifier
+ self.git_commit = None
if self.specifier is None:
self.specifier = 'master'
- def checkout(self, env):
+ def checkout(self, target):
flags = ''
redirect = ''
- if args.quiet:
+ if globals.quiet:
flags = '-q'
redirect = '>/dev/null'
- command('git clone --depth 0 %s %s/%s.git %s/src/%s' % (flags, self.git_dir, self.name, env.work_dir_cdist(), self.name))
- os.chdir('%s/src/%s' % (env.work_dir_cdist(), self.name))
+ 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))
command('git checkout %s %s %s' % (flags, self.specifier, redirect))
- command('git submodule init')
- command('git submodule update')
+ self.git_commit = command_and_read('git rev-parse --short=7 HEAD').readline().strip()
+ command('git submodule init --quiet')
+ command('git submodule update --quiet')
os.chdir(self.directory)
- proj = '%s/src/%s/%s' % (env.work_dir_cdist(), self.name, self.directory)
+ proj = '%s/src/%s/%s' % (target.directory, self.name, self.directory)
self.read_cscript('%s/cscript' % proj)
if os.path.exists('%s/wscript' % proj):
- f = open('%s/wscript' % proj, 'r')
- version = None
- while 1:
- l = f.readline()
- if l == '':
- break
-
- s = l.split()
- if len(s) == 3 and s[0] == "VERSION":
- self.version = Version(s[2])
-
- f.close()
+ v = read_wscript_variable(proj, "VERSION");
+ if v is not None:
+ self.version = Version(v)
def read_cscript(self, s):
self.cscript = {}
def set_version_in_wscript(version):
f = open('wscript', 'rw')
o = open('wscript.tmp', 'w')
- while 1:
+ while True:
l = f.readline()
if l == '':
break
command('dch -b -v %s-1 "New upstream release."' % version)
+def devel_to_git(project, filename):
+ if project.git_commit is not None:
+ filename = filename.replace('devel', '-%s' % project.git_commit)
+ return filename
+
+
#
# Command-line parser
#
-parser = argparse.ArgumentParser()
-parser.add_argument('command')
-parser.add_argument('-p', '--project', help='project name', required=True)
-parser.add_argument('-d', '--directory', help='directory within project repo', default='.')
-parser.add_argument('--beta', help='beta release', action='store_true')
-parser.add_argument('--full', help='full release', action='store_true')
-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')
-args = parser.parse_args()
-
-args.output = os.path.abspath(args.output)
-
-if args.project is None:
- error('you must specify -p or --project')
-
-project = Project(args.project, args.directory, args.checkout)
-
-if args.command == 'build':
- if args.target is None:
- error('you must specify -t or --target')
-
- target = Target(args.target)
- env = environment_for_target(target, None)
- project.checkout(env)
- env.build_dependencies(target, project)
- env.build(target, project)
-
- env.cleanup()
-
-elif args.command == 'package':
- if args.target is None:
- error('you must specify -t or --target')
+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",
+ "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('-d', '--directory', help='directory within project repo', default='.')
+ 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')
+ args = parser.parse_args()
+
+ if args.output.find(':') == -1:
+ # This isn't of the form host:path so make it absolute
+ args.output = os.path.abspath(args.output) + '/'
+
+ # Now, args.output is 'host:' 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')
- target = Target(args.target)
- env = environment_for_target(target, None)
-
- packages = env.package(target, project)
- if hasattr(packages, 'strip') or (not hasattr(packages, '__getitem__') and not hasattr(packages, '__iter__')):
- packages = [packages]
-
- if target.platform == 'linux':
- out = '%s/%s-%d' % (args.output, target.version, target.bits)
- try:
- os.makedirs(out)
- except:
- pass
- for p in packages:
- copyfile(p, '%s/%s' % (out, os.path.basename(p)))
- else:
- for p in packages:
- copyfile(p, '%s/%s' % (args.output, os.path.basename(p)))
+ globals.quiet = args.quiet
+ globals.command = args.command
+
+ project = Project(args.project, args.directory, args.checkout)
+
+ 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)
+ project.checkout(target)
+ target.build_dependencies(project)
+ target.build(project)
+ 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 = target.package(project)
+ 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:
+ os.makedirs(out)
+ except:
+ pass
+ for p in packages:
+ copyfile(p, '%s/%s' % (out, os.path.basename(devel_to_git(project, p))))
+ else:
+ for p in packages:
+ copyfile(p, '%s%s' % (args.output, os.path.basename(devel_to_git(project, p))))
- env.cleanup()
+ if not args.keep:
+ target.cleanup()
-elif args.command == 'release':
- if args.full is False and args.beta is False:
- error('you must specify --full or --beta')
+ elif globals.command == 'release':
+ if args.minor is False and args.micro is False:
+ raise Error('you must specify --minor or --micro')
- env = HostEnvironment()
- project.checkout(env)
+ target = SourceTarget()
+ project.checkout(target)
- version = project.version
- if args.full:
+ version = project.version
version.to_release()
- else:
- version.bump_beta()
+ 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)
+ 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))
+ command('git commit -a -m "Bump version"')
+ command('git tag -m "v%s" v%s' % (version, version))
- if args.full:
- version.bump_and_to_pre()
+ version.to_devel()
set_version_in_wscript(version)
command('git commit -a -m "Bump version"')
+ command('git push')
+ command('git push --tags')
- command('git push')
- command('git push --tags')
+ target.cleanup()
- env.cleanup()
+ elif globals.command == 'pot':
+ target = SourceTarget()
+ project.checkout(target)
-elif args.command == 'pot':
- env = HostEnvironment()
- project.checkout(env)
+ pots = project.cscript['make_pot'](target)
+ for p in pots:
+ copyfile(p, '%s%s' % (args.output, os.path.basename(p)))
- pots = project.cscript['make_pot'](env)
- for p in pots:
- copyfile(p, '%s/%s' % (args.output, os.path.basename(p)))
+ target.cleanup()
- env.cleanup()
+ elif globals.command == 'changelog':
+ target = SourceTarget()
+ project.checkout(target)
-elif args.command == 'changelog':
- env = HostEnvironment()
- project.checkout(env)
+ text = open('ChangeLog', 'r')
+ html = tempfile.NamedTemporaryFile()
+ versions = 8
- text = open('ChangeLog', 'r')
- html = open('%s/changelog.html' % args.output, 'w')
- versions = 8
-
- last = None
- changes = []
-
- while 1:
- 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.":
- if not "beta" in s[2]:
- 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
+ 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()
+ project.checkout(target)
+
+ outs = project.cscript['make_manual'](target)
+ for o in outs:
+ if os.path.isfile(o):
+ copyfile(o, '%s%s' % (args.output, os.path.basename(o)))
else:
- c = l.strip()
- if len(c) > 0:
- if c[0] == '*':
- changes.append(c[2:])
- else:
- changes[-1] += " " + c
-
- env.cleanup()
-
-elif args.command == 'manual':
- env = HostEnvironment()
- project.checkout(env)
-
- dirs = project.cscript['make_manual'](env)
- for d in dirs:
- copytree(d, '%s/%s' % (args.output, os.path.basename(d)))
-
- env.cleanup()
-
-elif args.command == 'doxygen':
- env = HostEnvironment()
- project.checkout(env)
-
- dirs = project.cscript['make_doxygen'](env)
- if hasattr(dirs, 'strip') or (not hasattr(dirs, '__getitem__') and not hasattr(dirs, '__iter__')):
- dirs = [dirs]
-
- for d in dirs:
- copytree(d, '%s/%s' % (args.output, 'doc'))
-
- env.cleanup()
-
-elif args.command == 'latest':
- env = HostEnvironment()
- project.checkout(env)
-
- f = command_and_read('git log --tags --simplify-by-decoration --pretty="%d"')
- t = f.readline()
- m = re.compile(".*\((.*)\).*").match(t)
- latest = None
- if m:
- tags = m.group(1).split(', ')
- for t in tags:
- if len(t) > 0 and t[0] == 'v':
- latest = t[1:]
-
- print latest
- env.cleanup()
-
-elif args.command == 'test':
- if args.target is None:
- error('you must specify -t or --target')
-
- target = Target(args.target)
- env = environment_for_target(target, '.')
- project.read_cscript('cscript')
- env.build(target, project)
-
-else:
- error('invalid command %s' % args.command)
+ copytree(o, '%s%s' % (args.output, os.path.basename(o)))
+
+ target.cleanup()
+
+ elif globals.command == 'doxygen':
+ target = SourceTarget()
+ project.checkout(target)
+
+ dirs = project.cscript['make_doxygen'](target)
+ if hasattr(dirs, 'strip') or (not hasattr(dirs, '__getitem__') and not hasattr(dirs, '__iter__')):
+ dirs = [dirs]
+
+ for d in dirs:
+ copytree(d, '%s%s' % (args.output, 'doc'))
+
+ target.cleanup()
+
+ elif globals.command == 'latest':
+ target = SourceTarget()
+ project.checkout(target)
+
+ 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)
+ target.test(project)
+ 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()
+ project.checkout(target)
+ print command_and_read('git rev-parse HEAD').readline().strip()[:7]
+ 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)