Newer
Older
Bryan O'Sullivan
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/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
import commands
Bryan O'Sullivan
committed
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'
Kyle McKay
committed
distcc = True
Bryan O'Sullivan
committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
Bryan O'Sullivan
committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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]
Paul Oppenheim
committed
if cpu.endswith('386'):
cpu = 'i386'
elif cpu.endswith('86'):
Bryan O'Sullivan
committed
cpu = 'i686'
elif cpu in ('athlon',):
cpu = 'i686'
elif cpu == 'Power Macintosh':
Paul Oppenheim
committed
cpu = 'ppc'
Bryan O'Sullivan
committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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:
Kyle McKay
committed
if self.distcc:
distcc = self.find_in_path('distcc')
baseonly = True
else:
distcc = []
baseonly = False
Bryan O'Sullivan
committed
if 'server' in build_dir:
Kyle McKay
committed
gcc33 = distcc + self.find_in_path('g++-3.3', 'g++', baseonly)
Bryan O'Sullivan
committed
args.update({'cxx':' '.join(gcc33), 'server':'TRUE',
'viewer':'FALSE'})
else:
Kyle McKay
committed
gcc41 = distcc + self.find_in_path('g++-4.1', 'g++', baseonly)
Bryan O'Sullivan
committed
args.update({'cxx': ' '.join(gcc41), 'server':'FALSE',
'viewer':'TRUE'})
#if simple:
# return (('cmake %(opts)s '
# '-DSERVER:BOOL=%(server)s '
# '-DVIEWER:BOOL=%(viewer)s '
# '%(dir)r') % args)
Bryan O'Sullivan
committed
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
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):
Paul Oppenheim
committed
if self.unattended == 'TRUE':
return 'universal'
else:
return UnixSetup.arch(self)
Bryan O'Sullivan
committed
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,
Paul Oppenheim
committed
universal='',
Bryan O'Sullivan
committed
type=self.build_type.upper()
)
Paul Oppenheim
committed
if self.unattended == 'TRUE':
args['universal'] = '-DCMAKE_OSX_ARCHITECTURES:STRING=\'i386;ppc\''
#if simple:
# return 'cmake %(opts)s %(dir)r' % args
Bryan O'Sullivan
committed
return ('cmake -G %(generator)r '
'-DCMAKE_BUILD_TYPE:STRING=%(type)s '
'-DSTANDALONE:BOOL=%(standalone)s '
'-DUNATTENDED:BOOL=%(unattended)s '
Paul Oppenheim
committed
'%(universal)s '
'%(opts)s %(dir)r' % args)
Bryan O'Sullivan
committed
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):
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'
Bryan O'Sullivan
committed
}
}
gens['vs2003'] = gens['vc71']
gens['vs2005'] = gens['vc80']
gens['vs2008'] = gens['vc90']
Bryan O'Sullivan
committed
def __init__(self):
super(WindowsSetup, self).__init__()
self._generator = None
Bryan O'Sullivan
committed
self.incredibuild = False
def _get_generator(self):
if self._generator is None:
for version in 'vc71 vc80 vc90'.split():
if self.find_visual_studio(version):
self._generator = version
print 'Building with ', self.gens[version]['gen']
break
else:
print >> sys.stderr, 'Cannot find a Visual Studio installation!'
eys.exit(1)
return self._generator
def _set_generator(self, gen):
self._generator = gen
generator = property(_get_generator, _set_generator)
Bryan O'Sullivan
committed
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
Bryan O'Sullivan
committed
return ('cmake -G "%(generator)s" '
'-DSTANDALONE:BOOL=%(standalone)s '
'-DUNATTENDED:BOOL=%(unattended)s '
'%(opts)s "%(dir)s"' % args)
def find_visual_studio(self, gen=None):
if gen is None:
gen = self._generator
gen = gen.lower()
Bryan O'Sullivan
committed
try:
import _winreg
key_str = (r'SOFTWARE\Microsoft\VisualStudio\%s\Setup\VS' %
self.gens[gen]['ver'])
Bryan O'Sullivan
committed
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
Bryan O'Sullivan
committed
except WindowsError, err:
print >> sys.stderr, "Didn't find ", self.gens[gen]['gen']
return ''
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
Bryan O'Sullivan
committed
# devenv.com is CLI friendly, devenv.exe... not so much.
return ('"%sdevenv.com" Secondlife.sln /build %s' %
(self.find_visual_studio(), self.build_type))
Bryan O'Sullivan
committed
# 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 = os.path.join('tools','vstool','VSTool.exe') \
+ ' --solution ' \
+ os.path.join(build_dir,'SecondLife.sln') \
+ ' --config RelWithDebInfo' \
+ ' --startup secondlife-bin'
Bryan O'Sullivan
committed
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__()
self.generator = 'vc71'
Bryan O'Sullivan
committed
def cmake_commandline(self, src_dir, build_dir, opts, simple):
dos_dir = commands.getoutput("cygpath -w %s" % src_dir)
Bryan O'Sullivan
committed
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
Bryan O'Sullivan
committed
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")
Kyle McKay
committed
-N | --no-distcc disable use of distcc
Bryan O'Sullivan
committed
-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,
Kyle McKay
committed
'?hNt:G:',
['help', 'standalone', 'no-distcc', 'unattended', 'type=', 'incredibuild', 'generator='])
Bryan O'Sullivan
committed
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
Kyle McKay
committed
elif o in ('-N', '--no-distcc'):
setup.distcc = False
Bryan O'Sullivan
committed
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(cmd)
Bryan O'Sullivan
committed
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:])