diff options
author | Bryan O'Sullivan <bos@lindenlab.com> | 2008-06-02 21:14:31 +0000 |
---|---|---|
committer | Bryan O'Sullivan <bos@lindenlab.com> | 2008-06-02 21:14:31 +0000 |
commit | 9db949eec327df4173fde3de934a87bedb0db13c (patch) | |
tree | aeffa0f0e68b1d2ceb74d460cbbd22652c9cd159 /indra/develop.py | |
parent | 419e13d0acaabf5e1e02e9b64a07648bce822b2f (diff) |
svn merge -r88066:88786 svn+ssh://svn.lindenlab.com/svn/linden/branches/cmake-9-merge
dataserver-is-deprecated
for-fucks-sake-whats-with-these-commit-markers
Diffstat (limited to 'indra/develop.py')
-rwxr-xr-x | indra/develop.py | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/indra/develop.py b/indra/develop.py new file mode 100755 index 0000000000..01e603a020 --- /dev/null +++ b/indra/develop.py @@ -0,0 +1,631 @@ +#!/usr/bin/env python +# +# @file develop.py +# @authors Bryan O'Sullivan, Mark Palange, Aaron Brashears +# @brief Fire and forget script to appropriately configure cmake for SL. +# +# $LicenseInfo:firstyear=2007&license=viewergpl$ +# +# Copyright (c) 2007, 2008 Linden Research, Inc. +# +# Second Life Viewer Source Code +# The source code in this file ("Source Code") is provided by Linden Lab +# to you under the terms of the GNU General Public License, version 2.0 +# ("GPL"), unless you have obtained a separate licensing agreement +# ("Other License"), formally executed by you and Linden Lab. Terms of +# the GPL can be found in doc/GPL-license.txt in this distribution, or +# online at http://secondlife.com/developers/opensource/gplv2 +# +# There are special exceptions to the terms and conditions of the GPL as +# it is applied to this Source Code. View the full text of the exception +# in the file doc/FLOSS-exception.txt in this software distribution, or +# online at http://secondlife.com/developers/opensource/flossexception +# +# By copying, modifying or distributing this software, you acknowledge +# that you have read and understood your obligations described above, +# and agree to abide by those obligations. +# +# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +# COMPLETENESS OR PERFORMANCE. +# $/LicenseInfo$ + + +import errno +import getopt +import os +import random +import re +import shutil +import socket +import sys + +class CommandError(Exception): + pass + + +def mkdir(path): + try: + os.mkdir(path) + return path + except OSError, err: + if err.errno != errno.EEXIST or not os.path.isdir(path): + raise + +def quote(opts): + return '"' + '" "'.join([ opt.replace('"', '') for opt in opts ]) + '"' + +class PlatformSetup(object): + generator = None + build_types = {} + for t in ('Debug', 'Release', 'RelWithDebInfo'): + build_types[t.lower()] = t + + build_type = build_types['relwithdebinfo'] + standalone = 'FALSE' + unattended = 'FALSE' + cmake_opts = [] + + def __init__(self): + self.script_dir = os.path.realpath( + os.path.dirname(__import__(__name__).__file__)) + + def os(self): + '''Return the name of the OS.''' + + raise NotImplemented('os') + + def arch(self): + '''Return the CPU architecture.''' + + return None + + def platform(self): + '''Return a stringified two-tuple of the OS name and CPU + architecture.''' + + ret = self.os() + if self.arch(): + ret += '-' + self.arch() + return ret + + def build_dirs(self): + '''Return the top-level directories in which builds occur. + + This can return more than one directory, e.g. if doing a + 32-bit viewer and server build on Linux.''' + + return ['build-' + self.platform()] + + def cmake_commandline(self, src_dir, build_dir, opts, simple): + '''Return the command line to run cmake with.''' + + args = dict( + dir=src_dir, + generator=self.generator, + opts=quote(opts), + standalone=self.standalone, + unattended=self.unattended, + type=self.build_type.upper(), + ) + if simple: + return 'cmake %(opts)s %(dir)r' % args + return ('cmake -DCMAKE_BUILD_TYPE:STRING=%(type)s ' + '-DSTANDALONE:BOOL=%(standalone)s ' + '-DUNATTENDED:BOOL=%(unattended)s ' + '-G %(generator)r %(opts)s %(dir)r' % args) + + def run(self, command, name=None): + '''Run a program. If the program fails, raise an exception.''' + ret = os.system(command) + if ret: + if name is None: + name = command.split(None, 1)[0] + if os.WIFEXITED(ret): + event = 'exited' + status = 'status %d' % os.WEXITSTATUS(ret) + elif os.WIFSIGNALED(ret): + event = 'was killed' + status = 'signal %d' % os.WTERMSIG(ret) + else: + event = 'died unexpectedly (!?)' + status = '16-bit status %d' % ret + raise CommandError('the command %r %s with %s' % + (name, event, status)) + + def run_cmake(self, args=[]): + '''Run cmake.''' + + # do a sanity check to make sure we have a generator + if not hasattr(self, 'generator'): + raise "No generator available for '%s'" % (self.__name__,) + cwd = os.getcwd() + created = [] + try: + for d in self.build_dirs(): + simple = True + if mkdir(d): + created.append(d) + simple = False + try: + os.chdir(d) + cmd = self.cmake_commandline(cwd, d, args, simple) + print 'Running %r in %r' % (cmd, d) + self.run(cmd, 'cmake') + finally: + os.chdir(cwd) + except: + # If we created a directory in which to run cmake and + # something went wrong, the directory probably just + # contains garbage, so delete it. + os.chdir(cwd) + for d in created: + print 'Cleaning %r' % d + shutil.rmtree(d) + raise + + def parse_build_opts(self, arguments): + opts, targets = getopt.getopt(arguments, 'o:', ['option=']) + build_opts = [] + for o, a in opts: + if o in ('-o', '--option'): + build_opts.append(a) + return build_opts, targets + + def run_build(self, opts, targets): + '''Build the default targets for this platform.''' + + raise NotImplemented('run_build') + + def cleanup(self): + '''Delete all build directories.''' + + cleaned = 0 + for d in self.build_dirs(): + if os.path.isdir(d): + print 'Cleaning %r' % d + shutil.rmtree(d) + cleaned += 1 + if not cleaned: + print 'Nothing to clean up!' + + def is_internal_tree(self): + '''Indicate whether we are building in an internal source tree.''' + + return os.path.isdir(os.path.join(self.script_dir, 'newsim')) + + +class UnixSetup(PlatformSetup): + '''Generic Unixy build instructions.''' + + def __init__(self): + super(UnixSetup, self).__init__() + self.generator = 'Unix Makefiles' + + def os(self): + return 'unix' + + def arch(self): + cpu = os.uname()[-1] + if cpu.endswith('86'): + cpu = 'i686' + elif cpu in ('athlon',): + cpu = 'i686' + elif cpu == 'Power Macintosh': + cpu = 'powerpc' + return cpu + + +class LinuxSetup(UnixSetup): + def __init__(self): + super(LinuxSetup, self).__init__() + + def os(self): + return 'linux' + + def build_dirs(self): + # Only build the server code if (a) we have it and (b) we're + # on 32-bit x86. + if self.arch() == 'i686' and self.is_internal_tree(): + return ['viewer-' + self.platform(), 'server-' + self.platform()] + else: + return ['viewer-' + self.platform()] + + def find_in_path(self, name, defval=None, basename=False): + for p in os.getenv('PATH', '/usr/bin').split(':'): + path = os.path.join(p, name) + if os.access(path, os.X_OK): + return [basename and os.path.basename(path) or path] + if defval: + return [defval] + return [] + + def cmake_commandline(self, src_dir, build_dir, opts, simple): + args = dict( + dir=src_dir, + generator=self.generator, + opts=quote(opts), + standalone=self.standalone, + unattended=self.unattended, + type=self.build_type.upper() + ) + if not self.is_internal_tree(): + args.update({'cxx':'g++', 'server':'FALSE', 'viewer':'TRUE'}) + else: + distcc = self.find_in_path('distcc') + if 'server' in build_dir: + gcc33 = distcc + self.find_in_path('g++-3.3', 'g++', True) + args.update({'cxx':' '.join(gcc33), 'server':'TRUE', + 'viewer':'FALSE'}) + else: + gcc41 = distcc + self.find_in_path('g++-4.1', 'g++', True) + args.update({'cxx': ' '.join(gcc41), 'server':'FALSE', + 'viewer':'TRUE'}) + if simple: + return 'cmake %(opts)s %(dir)r' % args + cmd = (('cmake -DCMAKE_BUILD_TYPE:STRING=%(type)s ' + '-G %(generator)r -DSERVER:BOOL=%(server)s ' + '-DVIEWER:BOOL=%(viewer)s -DSTANDALONE:BOOL=%(standalone)s ' + '-DUNATTENDED:BOOL=%(unattended)s ' + '%(opts)s %(dir)r') + % args) + if 'CXX' not in os.environ: + args.update({'cmd':cmd}) + cmd = ('CXX=%(cxx)r %(cmd)s' % args) + return cmd + + def run_build(self, opts, targets): + job_count = None + + for i in range(len(opts)): + if opts[i].startswith('-j'): + try: + job_count = int(opts[i][2:]) + except ValueError: + try: + job_count = int(opts[i+1]) + except ValueError: + job_count = True + + def get_cpu_count(): + count = 0 + for line in open('/proc/cpuinfo'): + if re.match(r'processor\s*:', line): + count += 1 + return count + + def localhost(): + count = get_cpu_count() + return 'localhost/' + str(count), count + + def get_distcc_hosts(): + try: + hosts = [] + name = os.getenv('DISTCC_DIR', '/etc/distcc') + '/hosts' + for l in open(name): + l = l[l.find('#')+1:].strip() + if l: hosts.append(l) + return hosts + except IOError: + return (os.getenv('DISTCC_HOSTS', '').split() or + [localhost()[0]]) + + def count_distcc_hosts(): + cpus = 0 + hosts = 0 + for host in get_distcc_hosts(): + m = re.match(r'.*/(\d+)', host) + hosts += 1 + cpus += m and int(m.group(1)) or 1 + return hosts, cpus + + def mk_distcc_hosts(): + '''Generate a list of LL-internal machines to build on.''' + loc_entry, cpus = localhost() + hosts = [loc_entry] + dead = [] + stations = [s for s in xrange(36) if s not in dead] + random.shuffle(stations) + hosts += ['station%d.lindenlab.com/2,lzo' % s for s in stations] + cpus += 2 * len(stations) + return ' '.join(hosts), cpus + + if job_count is None: + hosts, job_count = count_distcc_hosts() + if hosts == 1 and socket.gethostname().startswith('station'): + hosts, job_count = mk_distcc_hosts() + os.putenv('DISTCC_HOSTS', hosts) + opts.extend(['-j', str(job_count)]) + + if targets: + targets = ' '.join(targets) + else: + targets = 'all' + + for d in self.build_dirs(): + cmd = 'make -C %r %s %s' % (d, ' '.join(opts), targets) + print 'Running %r' % cmd + self.run(cmd) + + +class DarwinSetup(UnixSetup): + def __init__(self): + super(DarwinSetup, self).__init__() + self.generator = 'Xcode' + + def os(self): + return 'darwin' + + def arch(self): + return 'universal' + + def cmake_commandline(self, src_dir, build_dir, opts, simple): + arches = '' + args = dict( + arches=arches, + dir=src_dir, + generator=self.generator, + opts=quote(opts), + standalone=self.standalone, + unattended=self.unattended, + type=self.build_type.upper() + ) + if simple: + return 'cmake %(opts)s %(dir)r' % args + return ('cmake -G %(generator)r ' + '-DCMAKE_BUILD_TYPE:STRING=%(type)s ' + '-DSTANDALONE:BOOL=%(standalone)s ' + '-DUNATTENDED:BOOL=%(unattended)s ' + '%(arches)s %(opts)s %(dir)r' % args) + + def run_build(self, opts, targets): + cwd = os.getcwd() + if targets: + targets = ' '.join(['-target ' + repr(t) for t in targets]) + else: + targets = '' + cmd = ('xcodebuild -parallelizeTargets ' + '-configuration %s %s %s' % + (self.build_type, ' '.join(opts), targets)) + for d in self.build_dirs(): + try: + os.chdir(d) + print 'Running %r in %r' % (cmd, d) + self.run(cmd) + finally: + os.chdir(cwd) + + +class WindowsSetup(PlatformSetup): + def __init__(self): + super(WindowsSetup, self).__init__() + self.gens = { + 'vc71' : { + 'gen' : r'Visual Studio 7 .NET 2003', + 'ver' : r'7.1' + }, + 'vc80' : { + 'gen' : r'Visual Studio 8 2005', + 'ver' : r'8.0' + }, + 'vc90' : { + 'gen' : r'Visual Studio 9 2008', + 'ver' : r'9.0' + } + } + self.gens['vs2003'] = self.gens['vc71'] + self.gens['vs2005'] = self.gens['vc80'] + self.gens['vs2008'] = self.gens['vc90'] + + self.generator = 'vc71' + self.incredibuild = False + + def os(self): + return 'win32' + + def build_dirs(self): + return ['build-' + self.generator] + + def cmake_commandline(self, src_dir, build_dir, opts, simple): + args = dict( + dir=src_dir, + generator=self.gens[self.generator.lower()]['gen'], + opts=quote(opts), + standalone=self.standalone, + unattended=self.unattended, + ) + if simple: + return 'cmake %(opts)s "%(dir)s"' % args + return ('cmake -G "%(generator)s" ' + '-DSTANDALONE:BOOL=%(standalone)s ' + '-DUNATTENDED:BOOL=%(unattended)s ' + '%(opts)s "%(dir)s"' % args) + + def get_build_cmd(self): + if self.incredibuild: + config = self.build_type + if self.gens[self.generator]['ver'] in [ r'8.0', r'9.0' ]: + config = '\"%s|Win32\"' % config + + return "buildconsole Secondlife.sln /build %s" % config + + value = "" + try: + import _winreg + key_str = (r'SOFTWARE\Microsoft\VisualStudio\%s\Setup\VS' % + self.gens[self.generator.lower()]['ver']) + value_str = (r'EnvironmentDirectory') + print ('Reading VS environment from HKEY_LOCAL_MACHINE\%s\%s' % + (key_str, value_str)) + print key_str + + reg = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) + key = _winreg.OpenKey(reg, key_str) + value = _winreg.QueryValueEx(key, value_str)[0] + print 'Found: %s' % value + except WindowsError, err: + print "Didn't find Visual Studio installation." + + # devenv.com is CLI friendly, devenv.exe... not so much. + return '"' + value + 'devenv.com" Secondlife.sln /build %s' % self.build_type + + # this override of run exists because the PlatformSetup version + # uses Unix/Mac only calls. Freakin' os module! + def run(self, command, name=None): + '''Run a program. If the program fails, raise an exception.''' + ret = os.system(command) + if ret: + if name is None: + name = command.split(None, 1)[0] + raise CommandError('the command %r exited with %s' % + (name, ret)) + + def run_cmake(self, args=[]): + '''Override to add the vstool.exe call after running cmake.''' + PlatformSetup.run_cmake(self, args) + if self.unattended == 'FALSE': + for build_dir in self.build_dirs(): + vstool_cmd = ('tools\\vstool\\VSTool.exe' + ' --solution %s\\SecondLife.sln' + ' --config RelWithDebInfo' + ' --startup secondlife-bin' % build_dir) + print 'Running %r in %r' % (vstool_cmd, os.getcwd()) + self.run(vstool_cmd) + + def run_build(self, opts, targets): + cwd = os.getcwd() + build_cmd = self.get_build_cmd() + + for d in self.build_dirs(): + try: + os.chdir(d) + if targets: + for t in targets: + cmd = '%s /project %s %s' % (build_cmd, t, ' '.join(opts)) + print 'Running %r in %r' % (cmd, d) + self.run(cmd) + else: + cmd = '%s %s' % (build_cmd, ' '.join(opts)) + print 'Running %r in %r' % (cmd, d) + self.run(cmd) + finally: + os.chdir(cwd) + + +class CygwinSetup(WindowsSetup): + def __init__(self): + super(CygwinSetup, self).__init__() + + def cmake_commandline(self, src_dir, build_dir, opts, simple): + dos_dir = src_dir.split('/')[2:] + dos_dir[0] = dos_dir[0] + ":" + dos_dir = '/'.join(dos_dir) + args = dict( + dir=dos_dir, + generator=self.gens[self.generator.lower()]['gen'], + opts=quote(opts), + standalone=self.standalone, + unattended=self.unattended, + ) + if simple: + return 'cmake %(opts)s "%(dir)s"' % args + return ('cmake -G "%(generator)s" ' + '-DUNATTENDED:BOOl=%(unattended)s ' + '-DSTANDALONE:BOOL=%(standalone)s ' + '%(opts)s "%(dir)s"' % args) + +setup_platform = { + 'darwin': DarwinSetup, + 'linux2': LinuxSetup, + 'win32' : WindowsSetup, + 'cygwin' : CygwinSetup + } + + +usage_msg = ''' +Usage: develop.py [options] command [command-options] + +Options: + -h | --help print this help message + --standalone build standalone, without Linden prebuild libraries + --unattended build unattended, do not invoke any tools requiring + a human response + -t | --type=NAME build type ("Debug", "Release", or "RelWithDebInfo") + -G | --generator=NAME generator name + Windows: VC71 or VS2003 (default), VC80 (VS2005) or VC90 (VS2008) + Mac OS X: Xcode (default), Unix Makefiles + Linux: Unix Makefiles (default), KDevelop3 +Commands: + build configure and build default target + clean delete all build directories (does not affect sources) + configure configure project by running cmake + +If you do not specify a command, the default is "configure". +''' + +def main(arguments): + setup = setup_platform[sys.platform]() + try: + opts, args = getopt.getopt( + arguments, + '?ht:G:', + ['help', 'standalone', 'unattended', 'type=', 'incredibuild', 'generator=']) + except getopt.GetoptError, err: + print >> sys.stderr, 'Error:', err + sys.exit(1) + + for o, a in opts: + if o in ('-?', '-h', '--help'): + print usage_msg.strip() + sys.exit(0) + elif o in ('--standalone',): + setup.standalone = 'TRUE' + elif o in ('--unattended',): + setup.unattended = 'TRUE' + elif o in ('-t', '--type'): + try: + setup.build_type = setup.build_types[a.lower()] + except KeyError: + print >> sys.stderr, 'Error: unknown build type', repr(a) + print >> sys.stderr, 'Supported build types:' + types = setup.build_types.values() + types.sort() + for t in types: + print ' ', t + sys.exit(1) + elif o in ('-G', '--generator'): + setup.generator = a + elif o in ('--incredibuild'): + setup.incredibuild = True + else: + print >> sys.stderr, 'INTERNAL ERROR: unhandled option', repr(o) + sys.exit(1) + if not args: + setup.run_cmake() + return + try: + cmd = args.pop(0) + if cmd in ('cmake', 'configure'): + setup.run_cmake(args) + elif cmd == 'build': + for d in setup.build_dirs(): + if not os.path.exists(d): + raise CommandError('run "develop.py cmake" first') + setup.run_cmake() + opts, targets = setup.parse_build_opts(args) + setup.run_build(opts, targets) + elif cmd == 'clean': + if args: + raise CommandError('clean takes no arguments') + setup.cleanup() + else: + print >> sys.stderr, 'Error: unknown command', repr(arg) + print >> sys.stderr, "(run 'develop.py --help' for help)" + sys.exit(1) + except CommandError, err: + print >> sys.stderr, 'Error:', err + sys.exit(1) + + +if __name__ == '__main__': + main(sys.argv[1:]) |