aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/lib/python
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:44:50 -0500
committerJacek Antonelli2008-08-15 23:44:50 -0500
commit89fe5dab825a62a0e3fd8d248cbc91c65eb2a426 (patch)
treebcff14b7888d04a2fec799c59369f6095224bd08 /linden/indra/lib/python
parentSecond Life viewer sources 1.13.3.2 (diff)
downloadmeta-impy-89fe5dab825a62a0e3fd8d248cbc91c65eb2a426.zip
meta-impy-89fe5dab825a62a0e3fd8d248cbc91c65eb2a426.tar.gz
meta-impy-89fe5dab825a62a0e3fd8d248cbc91c65eb2a426.tar.bz2
meta-impy-89fe5dab825a62a0e3fd8d248cbc91c65eb2a426.tar.xz
Second Life viewer sources 1.14.0.0
Diffstat (limited to 'linden/indra/lib/python')
-rw-r--r--linden/indra/lib/python/indra/llmanifest.py562
1 files changed, 562 insertions, 0 deletions
diff --git a/linden/indra/lib/python/indra/llmanifest.py b/linden/indra/lib/python/indra/llmanifest.py
new file mode 100644
index 0000000..6697299
--- /dev/null
+++ b/linden/indra/lib/python/indra/llmanifest.py
@@ -0,0 +1,562 @@
1#!/usr/bin/python
2# @file llmanifest.py
3# @author Ryan Williams
4# @brief Library for specifying operations on a set of files.
5#
6# Copyright (c) 2006-2007, Linden Research, Inc.
7#
8# The source code in this file ("Source Code") is provided by Linden Lab
9# to you under the terms of the GNU General Public License, version 2.0
10# ("GPL"), unless you have obtained a separate licensing agreement
11# ("Other License"), formally executed by you and Linden Lab. Terms of
12# the GPL can be found in doc/GPL-license.txt in this distribution, or
13# online at http://secondlife.com/developers/opensource/gplv2
14#
15# There are special exceptions to the terms and conditions of the GPL as
16# it is applied to this Source Code. View the full text of the exception
17# in the file doc/FLOSS-exception.txt in this software distribution, or
18# online at http://secondlife.com/developers/opensource/flossexception
19#
20# By copying, modifying or distributing this software, you acknowledge
21# that you have read and understood your obligations described above,
22# and agree to abide by those obligations.
23#
24# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
25# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
26# COMPLETENESS OR PERFORMANCE.
27
28import commands
29import filecmp
30import fnmatch
31import getopt
32import glob
33import os
34import os.path
35import re
36import shutil
37import sys
38import tarfile
39
40def path_ancestors(path):
41 path = os.path.normpath(path)
42 result = []
43 while len(path) > 0:
44 result.append(path)
45 path, sub = os.path.split(path)
46 return result
47
48def proper_windows_path(path, current_platform = sys.platform):
49 """ This function takes an absolute Windows or Cygwin path and
50 returns a path appropriately formatted for the platform it's
51 running on (as determined by sys.platform)"""
52 path = path.strip()
53 drive_letter = None
54 rel = None
55 match = re.match("/cygdrive/([a-z])/(.*)", path)
56 if(not match):
57 match = re.match('([a-zA-Z]):\\\(.*)', path)
58 if(not match):
59 return None # not an absolute path
60 drive_letter = match.group(1)
61 rel = match.group(2)
62 if(current_platform == "cygwin"):
63 return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\\', '/')
64 else:
65 return drive_letter.upper() + ':\\' + rel.replace('/', '\\')
66
67def get_default_platform(dummy):
68 return {'linux2':'linux',
69 'linux1':'linux',
70 'cygwin':'windows',
71 'win32':'windows',
72 'darwin':'darwin'
73 }[sys.platform]
74
75def get_default_version(srctree):
76 # look up llversion.h and parse out the version info
77 paths = [os.path.join(srctree, x, 'llversion.h') for x in ['llcommon', '../llcommon', '../../indra/llcommon.h']]
78 for p in paths:
79 if os.path.exists(p):
80 contents = open(p, 'r').read()
81 major = re.search("LL_VERSION_MAJOR\s=\s([0-9]+)", contents).group(1)
82 minor = re.search("LL_VERSION_MINOR\s=\s([0-9]+)", contents).group(1)
83 patch = re.search("LL_VERSION_PATCH\s=\s([0-9]+)", contents).group(1)
84 build = re.search("LL_VERSION_BUILD\s=\s([0-9]+)", contents).group(1)
85 return major, minor, patch, build
86
87
88ARGUMENTS=[
89 dict(name='actions',
90 description="""This argument specifies the actions that are to be taken when the
91 script is run. The meaningful actions are currently:
92 copy - copies the files specified by the manifest into the
93 destination directory.
94 package - bundles up the files in the destination directory into
95 an installer for the current platform
96 unpacked - bundles up the files in the destination directory into
97 a simple tarball
98 Example use: %(name)s --actions="copy unpacked" """,
99 default="copy package"),
100 dict(name='arch',
101 description="""This argument is appended to the platform string for
102 determining which manifest class to run.
103 Example use: %(name)s --arch=i686
104 On Linux this would try to use Linux_i686Manifest.""",
105 default=""),
106 dict(name='configuration',
107 description="""The build configuration used. Only used on OS X for
108 now, but it could be used for other platforms as well.""",
109 default="Universal"),
110 dict(name='grid',
111 description="""Which grid the client will try to connect to. Even
112 though it's not strictly a grid, 'firstlook' is also an acceptable
113 value for this parameter.""",
114 default=""),
115 dict(name='installer_name',
116 description=""" The name of the file that the installer should be
117 packaged up into. Only used on Linux at the moment.""",
118 default=None),
119 dict(name='login_url',
120 description="""The url that the login screen displays in the client.""",
121 default=None),
122 dict(name='platform',
123 description="""The current platform, to be used for looking up which
124 manifest class to run.""",
125 default=get_default_platform),
126 dict(name='version',
127 description="""This specifies the version of Second Life that is
128 being packaged up.""",
129 default=get_default_version)
130 ]
131
132def usage(srctree=""):
133 nd = {'name':sys.argv[0]}
134 print """Usage:
135 %(name)s [options] [destdir]
136 Options:
137 """ % nd
138 for arg in ARGUMENTS:
139 default = arg['default']
140 if hasattr(default, '__call__'):
141 default = "(computed value) \"" + str(default(srctree)) + '"'
142 elif default is not None:
143 default = '"' + default + '"'
144 print "\t--%s Default: %s\n\t%s\n" % (
145 arg['name'],
146 default,
147 arg['description'] % nd)
148
149def main(argv=None, srctree='.', dsttree='./dst'):
150 if(argv == None):
151 argv = sys.argv
152
153 print "Source tree:", srctree
154 print "Destination tree:", dsttree
155
156 option_names = [arg['name'] + '=' for arg in ARGUMENTS]
157 option_names.append('help')
158 options, remainder = getopt.getopt(argv[1:], "", option_names)
159 if len(remainder) >= 1:
160 dsttree = remainder[0]
161
162 # convert options to a hash
163 args = {}
164 for opt in options:
165 args[opt[0].replace("--", "")] = opt[1]
166
167 # early out for help
168 if args.has_key('help'):
169 # *TODO: it is a huge hack to pass around the srctree like this
170 usage(srctree)
171 return
172
173 # defaults
174 for arg in ARGUMENTS:
175 if not args.has_key(arg['name']):
176 default = arg['default']
177 if hasattr(default, '__call__'):
178 default = default(srctree)
179 if default is not None:
180 args[arg['name']] = default
181
182 # fix up version
183 if args.has_key('version') and type(args['version']) == str:
184 args['version'] = args['version'].split('.')
185
186 # default and agni are default
187 if args['grid'] in ['default', 'agni']:
188 args['grid'] = ''
189
190 if args.has_key('actions'):
191 args['actions'] = args['actions'].split()
192
193 # debugging
194 for opt in args:
195 print "Option:", opt, "=", args[opt]
196
197 wm = LLManifest.for_platform(args['platform'], args.get('arch'))(srctree, dsttree, args)
198 wm.do(*args['actions'])
199 return 0
200
201class LLManifestRegistry(type):
202 def __init__(cls, name, bases, dct):
203 super(LLManifestRegistry, cls).__init__(name, bases, dct)
204 match = re.match("(\w+)Manifest", name)
205 if(match):
206 cls.manifests[match.group(1).lower()] = cls
207
208class LLManifest(object):
209 __metaclass__ = LLManifestRegistry
210 manifests = {}
211 def for_platform(self, platform, arch = None):
212 if arch:
213 platform = platform + '_' + arch
214 return self.manifests[platform.lower()]
215 for_platform = classmethod(for_platform)
216
217 def __init__(self, srctree, dsttree, args):
218 super(LLManifest, self).__init__()
219 self.args = args
220 self.file_list = []
221 self.excludes = []
222 self.actions = []
223 self.src_prefix = [srctree]
224 self.dst_prefix = [dsttree]
225 self.created_paths = []
226
227 def construct(self):
228 """ Meant to be overriden by LLManifest implementors with code that
229 constructs the complete destination hierarchy."""
230 pass # override this method
231
232 def exclude(self, glob):
233 """ Excludes all files that match the glob from being included
234 in the file list by path()."""
235 self.excludes.append(glob)
236
237 def prefix(self, src='', dst=None):
238 """ Pushes a prefix onto the stack. Until end_prefix is
239 called, all relevant method calls (esp. to path()) will prefix
240 paths with the entire prefix stack. Source and destination
241 prefixes can be different, though if only one is provided they
242 are both equal. To specify a no-op, use an empty string, not
243 None."""
244 if(dst == None):
245 dst = src
246 self.src_prefix.append(src)
247 self.dst_prefix.append(dst)
248 return True # so that you can wrap it in an if to get indentation
249
250 def end_prefix(self, descr=None):
251 """Pops a prefix off the stack. If given an argument, checks
252 the argument against the top of the stack. If the argument
253 matches neither the source or destination prefixes at the top
254 of the stack, then misnesting must have occurred and an
255 exception is raised."""
256 # as an error-prevention mechanism, check the prefix and see if it matches the source or destination prefix. If not, improper nesting may have occurred.
257 src = self.src_prefix.pop()
258 dst = self.dst_prefix.pop()
259 if descr and not(src == descr or dst == descr):
260 raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'"
261
262 def get_src_prefix(self):
263 """ Returns the current source prefix."""
264 return os.path.join(*self.src_prefix)
265
266 def get_dst_prefix(self):
267 """ Returns the current destination prefix."""
268 return os.path.join(*self.dst_prefix)
269
270 def src_path_of(self, relpath):
271 """Returns the full path to a file or directory specified
272 relative to the source directory."""
273 return os.path.join(self.get_src_prefix(), relpath)
274
275 def dst_path_of(self, relpath):
276 """Returns the full path to a file or directory specified
277 relative to the destination directory."""
278 return os.path.join(self.get_dst_prefix(), relpath)
279
280 def ensure_src_dir(self, reldir):
281 """Construct the path for a directory relative to the
282 source path, and ensures that it exists. Returns the
283 full path."""
284 path = os.path.join(self.get_src_prefix(), reldir)
285 self.cmakedirs(path)
286 return path
287
288 def ensure_dst_dir(self, reldir):
289 """Construct the path for a directory relative to the
290 destination path, and ensures that it exists. Returns the
291 full path."""
292 path = os.path.join(self.get_dst_prefix(), reldir)
293 self.cmakedirs(path)
294 return path
295
296 def run_command(self, command):
297 """ Runs an external command, and returns the output. Raises
298 an exception if the command reurns a nonzero status code. For
299 debugging/informational purpoases, prints out the command's
300 output as it is received."""
301 print "Running command:", command
302 fd = os.popen(command, 'r')
303 lines = []
304 while True:
305 lines.append(fd.readline())
306 if(lines[-1] == ''):
307 break
308 else:
309 print lines[-1],
310 output = ''.join(lines)
311 status = fd.close()
312 if(status):
313 raise RuntimeError, "Command " + command + " returned non-zero status (" + str(status) + ")"
314 return output
315
316 def created_path(self, path):
317 """ Declare that you've created a path in order to
318 a) verify that you really have created it
319 b) schedule it for cleanup"""
320 if not os.path.exists(path):
321 raise RuntimeError, "Should be something at path " + path
322 self.created_paths.append(path)
323
324 def put_in_file(self, contents, dst):
325 # write contents as dst
326 f = open(self.dst_path_of(dst), "wbU")
327 f.write(contents)
328 f.close()
329
330 def replace_in(self, src, dst=None, searchdict={}):
331 if(dst == None):
332 dst = src
333 # read src
334 f = open(self.src_path_of(src), "rbU")
335 contents = f.read()
336 f.close()
337 # apply dict replacements
338 for old, new in searchdict.iteritems():
339 contents = contents.replace(old, new)
340 self.put_in_file(contents, dst)
341 self.created_paths.append(dst)
342
343 def copy_action(self, src, dst):
344 if(src and (os.path.exists(src) or os.path.islink(src))):
345 # ensure that destination path exists
346 self.cmakedirs(os.path.dirname(dst))
347 self.created_paths.append(dst)
348 if(not os.path.isdir(src)):
349 self.ccopy(src,dst)
350 else:
351 # src is a dir
352 self.ccopytree(src,dst)
353 else:
354 print "Doesn't exist:", src
355
356 def package_action(self, src, dst):
357 pass
358
359 def copy_finish(self):
360 pass
361
362 def package_finish(self):
363 pass
364
365 def unpacked_finish(self):
366 unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % {
367 'plat':self.args['platform'],
368 'vers':'_'.join(self.args['version'])}
369 print "Creating unpacked file:", unpacked_file_name
370 # could add a gz here but that doubles the time it takes to do this step
371 tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:')
372 # add the entire installation package, at the very top level
373 tf.add(self.get_dst_prefix(), "")
374 tf.close()
375
376 def cleanup_finish(self):
377 """ Delete paths that were specified to have been created by this script"""
378 for c in self.created_paths:
379 # *TODO is this gonna be useful?
380 print "Cleaning up " + c
381
382 def process_file(self, src, dst):
383 if(self.includes(src, dst)):
384# print src, "=>", dst
385 for action in self.actions:
386 methodname = action + "_action"
387 method = getattr(self, methodname, None)
388 if method is not None:
389 method(src, dst)
390 self.file_list.append([src, dst])
391 else:
392 print "Excluding: ", src, dst
393
394
395 def process_directory(self, src, dst):
396 if(not self.includes(src, dst)):
397 print "Excluding: ", src, dst
398 return
399 names = os.listdir(src)
400 self.cmakedirs(dst)
401 errors = []
402 for name in names:
403 srcname = os.path.join(src, name)
404 dstname = os.path.join(dst, name)
405 if os.path.isdir(srcname):
406 self.process_directory(srcname, dstname)
407 else:
408 self.process_file(srcname, dstname)
409
410
411
412 def includes(self, src, dst):
413 if src:
414 for excl in self.excludes:
415 if fnmatch.fnmatch(src, excl):
416 return False
417 return True
418
419 def remove(self, *paths):
420 for path in paths:
421 if(os.path.exists(path)):
422 print "Removing path", path
423 if(os.path.isdir(path)):
424 shutil.rmtree(path)
425 else:
426 os.remove(path)
427
428 def ccopy(self, src, dst):
429 """ Copy a single file or symlink. Uses filecmp to skip copying for existing files."""
430 if os.path.islink(src):
431 linkto = os.readlink(src)
432 if(os.path.islink(dst) or os.path.exists(dst)):
433 os.remove(dst) # because symlinking over an existing link fails
434 os.symlink(linkto, dst)
435 else:
436 # Don't recopy file if it's up-to-date.
437 # If we seem to be not not overwriting files that have been
438 # updated, set the last arg to False, but it will take longer.
439 if(os.path.exists(dst) and filecmp.cmp(src, dst, True)):
440 return
441 # only copy if it's not excluded
442 if(self.includes(src, dst)):
443 shutil.copy2(src, dst)
444
445 def ccopytree(self, src, dst):
446 """Direct copy of shutil.copytree with the additional
447 feature that the destination directory can exist. It
448 is so dumb that Python doesn't come with this. Also it
449 implements the excludes functionality."""
450 if(not self.includes(src, dst)):
451 return
452 names = os.listdir(src)
453 self.cmakedirs(dst)
454 errors = []
455 for name in names:
456 srcname = os.path.join(src, name)
457 dstname = os.path.join(dst, name)
458 try:
459 if os.path.isdir(srcname):
460 self.ccopytree(srcname, dstname)
461 else:
462 self.ccopy(srcname, dstname)
463 # XXX What about devices, sockets etc.?
464 except (IOError, os.error), why:
465 errors.append((srcname, dstname, why))
466 if errors:
467 raise RuntimeError, errors
468
469
470 def cmakedirs(self, path):
471 """Ensures that a directory exists, and doesn't throw an exception
472 if you call it on an existing directory."""
473# print "making path: ", path
474 path = os.path.normpath(path)
475 self.created_paths.append(path)
476 if not os.path.exists(path):
477 os.makedirs(path)
478
479 def find_existing_file(self, *list):
480 for f in list:
481 if(os.path.exists(f)):
482 return f
483
484 def contents_of_tar(self, src_tar, dst_dir):
485 """ Extracts the contents of the tarfile (specified
486 relative to the source prefix) into the directory
487 specified relative to the destination directory."""
488 self.check_file_exists(src_tar)
489 tf = tarfile.open(self.src_path_of(src_tar), 'r')
490 for member in tf.getmembers():
491 tf.extract(member, self.ensure_dst_dir(dst_dir))
492 # TODO get actions working on these dudes, perhaps we should extract to a temporary directory and then process_directory on it?
493 self.file_list.append([src_tar,
494 self.dst_path_of(os.path.join(dst_dir,member.name))])
495 tf.close()
496
497
498 def wildcard_regex(self, src_glob, dst_glob):
499 # print "regex_pair:", src_glob, dst_glob
500 src_re = re.escape(src_glob)
501 src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)')
502 dst_temp = dst_glob
503 i = 1
504 while(dst_temp.count("*") > 0):
505 dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1)
506 i = i+1
507 # print "regex_result:", src_re, dst_temp
508 return re.compile(src_re), dst_temp
509
510 def check_file_exists(self, path):
511 if(not os.path.exists(path) and not os.path.islink(path)):
512 raise RuntimeError, "Path " + path + " doesn't exist"
513
514
515 wildcard_pattern = re.compile('\*')
516 def expand_globs(self, src, dst):
517 def fw_slash(str):
518 return str.replace('\\', '/')
519 def os_slash(str):
520 return str.replace('/', os.path.sep)
521 dst = fw_slash(dst)
522 src = fw_slash(src)
523 src_list = glob.glob(src)
524 src_re, d_template = self.wildcard_regex(src, dst)
525 for s in src_list:
526 s = fw_slash(s)
527 d = src_re.sub(d_template, s)
528 #print "s:",s, "d_t", d_template, "dst", dst, "d", d
529 yield os_slash(s), os_slash(d)
530
531 def path(self, src, dst=None):
532 print "Processing", src, "=>", dst
533 if dst == None:
534 dst = src
535 dst = os.path.join(self.get_dst_prefix(), dst)
536 src = os.path.join(self.get_src_prefix(), src)
537
538 # expand globs
539 if(self.wildcard_pattern.search(src)):
540 for s,d in self.expand_globs(src, dst):
541 self.process_file(s, d)
542 else:
543 # if we're specifying a single path (not a glob),
544 # we should error out if it doesn't exist
545 self.check_file_exists(src)
546 # if it's a directory, recurse through it
547 if(os.path.isdir(src)):
548 self.process_directory(src, dst)
549 else:
550 self.process_file(src, dst)
551
552
553 def do(self, *actions):
554 self.actions = actions
555 self.construct()
556 # perform finish actions
557 for action in self.actions:
558 methodname = action + "_finish"
559 method = getattr(self, methodname, None)
560 if method is not None:
561 method()
562 return self.file_list