Merge Environment and target; give up on remote/host/chroot pretence in favour of...
authorCarl Hetherington <cth@carlh.net>
Tue, 18 Jun 2013 18:39:33 +0000 (19:39 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 18 Jun 2013 18:39:33 +0000 (19:39 +0100)
cdist

diff --git a/cdist b/cdist
index 4bb2597afbf6e267963b72aff04cc834e8b90348..cfb4450cdef3992c6138354f00de08cf37024a39 100755 (executable)
--- a/cdist
+++ b/cdist
 # 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
+LINUX_DIR_IN_CHROOT = '/home/carl'
+LINUX_CHROOT_PREFIX = '/home/carl/Environments'
 WINDOWS_ENVIRONMENT_PREFIX = '/home/carl/Environments/windows'
-# Git prefix
-GIT_DIR = 'ssh://houllier/home/carl/git'
+GIT_PREFIX = 'ssh://houllier/home/carl/git'
 OSX_BUILD_HOST = 'carl@192.168.1.202'
-DIR_ON_HOST = '/Users/carl/cdist'
+OSX_DIR_IN_HOST = '/Users/carl/cdist'
 OSX_ENVIRONMENT_PREFIX = '/Users/carl/Environments/osx'
 OSX_SDK_PREFIX = '/Users/carl/SDK'
 
@@ -147,14 +143,32 @@ class Version:
 
         return s
 
-
 #
-# Environment
+# Targets
 #
 
-class Environment(object):
-    def __init__(self):
+class Target(object):
+    def __init__(self, platform):
+        self.platform = platform
+        # Environment variables that we will use when we call cscripts
         self.variables = {}
+        # Prefix to which builds should be installed by cscripts
+        self.install_prefix = '.'
+
+    def build_dependencies(self, project):
+        cwd = os.getcwd()
+        if 'dependencies' in project.cscript:
+            print project.cscript['dependencies'](self)
+            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)
+                self.build(dep)
+        os.chdir(cwd)
+
+    def build(self, project):
+        project.cscript['build'](self)
 
     def set(self, a, b):
         self.variables[a] = b
@@ -170,92 +184,127 @@ class Environment(object):
             e += '%s=%s ' % (k, v)
         return e
 
-    def work_dir_cdist(self, sub):
-        assert(false)
+    def cleanup(self):
+        pass
 
-    def work_dir_cscript(self):
-        assert(false)
+# 
+# Windows
+#
 
-    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)
+class WindowsTarget(Target):
+    def __init__(self, bits, directory = None):
+        super(WindowsTarget, self).__init__('windows')
+        self.bits = bits
+        if directory is None:
+            self.directory = tempfile.mkdtemp()
+            self.rmdir = True
+        else:
+            self.directory = directory
+            self.rmdir = False
+        
+        self.windows_prefix = '%s/%d' % (WINDOWS_ENVIRONMENT_PREFIX, self.bits)
+        if not os.path.exists(self.windows_prefix):
+            error('windows prefix %s does not exist' % target.windows_prefix)
+            
+        if self.bits == 32:
+            mingw_name = 'i686'
+        else:
+            mingw_name = 'x86_64'
 
-    def build(self, target, project):
-        project.cscript['build'](self, target)
+        mingw_path = '/mingw/%d/bin' % self.bits
+        mingw_prefixes = ['/mingw/%d' % self.bits, '/mingw/%d/%s-w64-mingw32' % (bits, mingw_name)]
 
-    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))
-        else:
-            project.cscript['build'](self, target)
-            return project.cscript['package'](self, target, project.version)
+        self.set('PKG_CONFIG_LIBDIR', '%s/lib/pkgconfig' % self.windows_prefix)
+        self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig' % self.work_dir_cscript())
+        self.set('PATH', '%s/bin:%s:%s' % (self.windows_prefix, mingw_path, os.environ['PATH']))
+        self.set('CC', '%s-w64-mingw32-gcc' % mingw_name)
+        self.set('CXX', '%s-w64-mingw32-g++' % mingw_name)
+        self.set('LD', '%s-w64-mingw32-ld' % mingw_name)
+        self.set('RANLIB', '%s-w64-mingw32-ranlib' % mingw_name)
+        self.set('WINRC', '%s-w64-mingw32-windres' % mingw_name)
+        cxx = '-I%s/include -I%s/include' % (self.windows_prefix, self.work_dir_cscript())
+        link = '-L%s/lib -L%s/lib' % (self.windows_prefix, self.work_dir_cscript())
+        for p in mingw_prefixes:
+            cxx += ' -I%s/include' % p
+            link += ' -L%s/lib' % p
+        self.set('CXXFLAGS', '"%s"' % cxx)
+        self.set('LINKFLAGS', '"%s"' % link)
+
+    def work_dir_cdist(self):
+        return self.directory
+
+    def work_dir_cscript(self):
+        return self.directory
+
+    def command(self, c):
+        log('host -> %s' % c)
+        command('%s %s' % (self.variables_string(), c))
 
     def cleanup(self):
-        pass
+        if self.rmdir:
+            rmtree(self.directory)
+
+    def package(self, project):
+        project.checkout(self)
+        self.build_dependencies(project)
+        project.cscript['build'](self)
+        return project.cscript['package'](self, project.version)
 
 #
-# 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):
+        super(LinuxTarget, self).__init__('linux')
+        self.distro = distro
+        self.version = version
+        self.bits = bits
+        self.chroot = '%s-%s-%d' % (self.distro, self.version, self.bits)
 
-        # 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 work_dir_cdist(self):
-        return '%s/%s%s' % (self.chroot_dir, self.chroot, self.dir_in_chroot)
+        return '%s/%s%s' % (LINUX_CHROOT_PREFIX, self.chroot, LINUX_DIR_IN_CHROOT)
 
     def work_dir_cscript(self):
-        return self.dir_in_chroot
+        return LINUX_DIR_IN_CHROOT
 
     def command(self, c):
         # Work out the cwd for the chrooted command
         cwd = os.getcwd()
-        prefix = '%s/%s' % (self.chroot_dir, self.chroot)
+        prefix = '%s/%s' % (LINUX_CHROOT_PREFIX, 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))
 
+    def package(self, target):
+        project.checkout(self)
+        self.build_dependencies(project)
+        project.cscript['build'](self)
+        return project.cscript['package'](self, project.version)
+
 
 #
-# RemoteEnvironment
+# OS X
 #
 
-class RemoteEnvironment(Environment):
-    def __init__(self, host):
-        super(RemoteEnvironment, self).__init__()
-        self.host = host
-        self.dir_on_host = DIR_ON_HOST
+class OSXTarget(Target):
+    def __init__(self):
+        super(OSXTarget, self).__init__('osx')
+
         self.host_mount_dir = tempfile.mkdtemp()
         self.osx_sdk = '10.6'
 
         # Mount the remote host on host_mount_dir
-        command('sshfs %s:%s %s' % (self.host, self.dir_on_host, self.host_mount_dir))
+        command('sshfs %s:%s %s' % (OSX_BUILD_HOST, OSX_DIR_IN_HOST, self.host_mount_dir))
         for g in glob.glob('%s/*' % self.host_mount_dir):
             rmtree(g)
 
@@ -263,11 +312,11 @@ class RemoteEnvironment(Environment):
         enviro = '%s/%s' % (OSX_ENVIRONMENT_PREFIX, self.osx_sdk)
 
         # Environment variables
-        self.set('CCFLAGS', '"-I%s/include -I%s/include %s"' % (self.dir_on_host, enviro, flags))
-        self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (self.dir_on_host, enviro, flags))
-        self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.dir_on_host, enviro, flags))
-        self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (self.dir_on_host, enviro, flags))
-        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"' % (OSX_DIR_IN_HOST, enviro, flags))
+        self.set('CXXFLAGS', '"-I%s/include -I%s/include %s"' % (OSX_DIR_IN_HOST, enviro, flags))
+        self.set('LDFLAGS', '"-L%s/lib -L%s/lib %s"' % (OSX_DIR_IN_HOST, enviro, flags))
+        self.set('LINKFLAGS', '"-L%s/lib -L%s/lib %s"' % (OSX_DIR_IN_HOST, enviro, flags))
+        self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/lib/pkgconfig' % (OSX_DIR_IN_HOST, enviro))
         self.set('PATH', '$PATH:/usr/bin:/sbin:/usr/local/bin:%s/bin' % enviro)
         self.set('MACOSX_DEPLOYMENT_TARGET', self.osx_sdk)
 
@@ -275,7 +324,19 @@ class RemoteEnvironment(Environment):
         return self.host_mount_dir
 
     def work_dir_cscript(self):
-        return self.dir_on_host
+        return OSX_DIR_IN_HOST
+
+    def package(self, project):
+        project.checkout(self)
+        self.build_dependencies(target, project)
+        # We have to build 32- and 64-bit versions
+        # and then stick them together to make a universal binary
+        target.bits = 32
+        self.install_prefix = '32'
+        project.cscript['build'](self, target)
+        target.bits = 64
+        self.install_prefix = '64'
+        project.cscript['build'](self, target)
 
     def command(self, c):
         # Work out the cwd for the chrooted command
@@ -284,7 +345,7 @@ class RemoteEnvironment(Environment):
         cwd = cwd[len(self.host_mount_dir):]
 
         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))
+        command('ssh %s -- "cd %s%s; %s %s"' % (OSX_BUILD_HOST, OSX_DIR_IN_HOST, cwd, self.variables_string(True), c))
 
     def cleanup(self):
         os.chdir('/')
@@ -292,18 +353,13 @@ class RemoteEnvironment(Environment):
         rmdir(self.host_mount_dir)
 
 #
-# 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
+class SourceTarget(Target):
+    def __init__(self):
+        super(SourceTarget, self).__init__('source')
+        self.directory = tempfile.mkdtemp()
 
     def work_dir_cdist(self):
         return self.directory
@@ -316,74 +372,35 @@ class HostEnvironment(Environment):
         command('%s %s' % (self.variables_string(), c))
 
     def cleanup(self):
-        if self.rmdir:
-            rmtree(self.directory)
-
-
-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 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)
-
-
-#
-# 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()
+        rmtree(self.directory)
+
+    def package(self, project):
+        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))
+
+
+# @param s Target string:
+#       windows-{32,64}
+#    or ubuntu-version-{32,64}
+#    or debian-version-{32,64}
+#    or osx
+#    or source      
+def target_factory(s):
+    if s.startswith('windows-'):
+        return WindowsTarget(int(s.split('-')[1]))
+    elif s.startswith('ubuntu-') or s.startswith('debian-'):
+        p = s.split('-')
+        return LinuxTarget(p[0], p[1], int(p[2]))
+    elif s == 'osx':
+        return OSXTarget()
+    elif s == 'source':
+        return SourceTarget()
 
     return None
 
+
 #
 # Project
 #
@@ -392,26 +409,25 @@ class Project(object):
     def __init__(self, name, directory, specifier=None):
         self.name = name
         self.directory = directory
-        self.git_dir = GIT_DIR
         self.version = None
         self.specifier = specifier
         if self.specifier is None:
             self.specifier = 'master'
 
-    def checkout(self, env):
+    def checkout(self, target):
         flags = ''
         redirect = ''
         if args.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 --depth 0 %s %s/%s.git %s/src/%s' % (flags, GIT_PREFIX, self.name, target.work_dir_cdist(), self.name))
+        os.chdir('%s/src/%s' % (target.work_dir_cdist(), self.name))
         command('git checkout %s %s %s' % (flags, self.specifier, redirect))
         command('git submodule init')
         command('git submodule update')
         os.chdir(self.directory)
 
-        proj = '%s/src/%s/%s' % (env.work_dir_cdist(), self.name, self.directory)
+        proj = '%s/src/%s/%s' % (target.work_dir_cdist(), self.name, self.directory)
 
         self.read_cscript('%s/cscript' % proj)
         
@@ -502,22 +518,19 @@ 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()
+    target = target_factory(args.target)
+    project.checkout(target)
+    target.build_dependencies(project)
+    target.build(project)
+    target.cleanup()
 
 elif args.command == 'package':
     if args.target is None:
         error('you must specify -t or --target')
         
-    target = Target(args.target)
-    env = environment_for_target(target, None)
+    target = target_factory(args.target)
 
-    packages = env.package(target, project)
+    packages = target.package(project)
     if hasattr(packages, 'strip') or (not hasattr(packages, '__getitem__') and not hasattr(packages, '__iter__')):
         packages = [packages]
 
@@ -533,14 +546,14 @@ elif args.command == 'package':
         for p in packages:
             copyfile(p, '%s/%s' % (args.output, os.path.basename(p)))
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'release':
     if args.full is False and args.beta is False:
         error('you must specify --full or --beta')
 
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
     version = project.version
     if args.full:
@@ -563,21 +576,21 @@ elif args.command == 'release':
     command('git push')
     command('git push --tags')
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'pot':
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
-    pots = project.cscript['make_pot'](env)
+    pots = project.cscript['make_pot'](target)
     for p in pots:
         copyfile(p, '%s/%s' % (args.output, os.path.basename(p)))
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'changelog':
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
     text = open('ChangeLog', 'r')
     html = open('%s/changelog.html' % args.output, 'w')
@@ -614,34 +627,34 @@ elif args.command == 'changelog':
                     else:
                         changes[-1] += " " + c
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'manual':
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
-    dirs = project.cscript['make_manual'](env)
+    dirs = project.cscript['make_manual'](target)
     for d in dirs:
         copytree(d, '%s/%s' % (args.output, os.path.basename(d)))
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'doxygen':
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
-    dirs = project.cscript['make_doxygen'](env)
+    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'))
 
-    env.cleanup()
+    target.cleanup()
 
 elif args.command == 'latest':
-    env = HostEnvironment()
-    project.checkout(env)
+    target = SourceTarget()
+    project.checkout(target)
 
     f = command_and_read('git log --tags --simplify-by-decoration --pretty="%d"')
     t = f.readline()
@@ -654,16 +667,15 @@ elif args.command == 'latest':
                 latest = t[1:]
 
     print latest
-    env.cleanup()
+    target.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)
+    target.build(project)
 
 else:
     error('invalid command %s' % args.command)