summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/automated_build_scripts/opensrc-build.sh391
-rwxr-xr-xscripts/build_version.py54
-rwxr-xr-xscripts/install.py1150
-rw-r--r--scripts/messages/message_template.msg244
-rw-r--r--[-rwxr-xr-x]scripts/setup-path.py39
-rw-r--r--[-rwxr-xr-x]scripts/template_verifier.py85
-rwxr-xr-xscripts/update_version_files.py80
7 files changed, 1946 insertions, 97 deletions
diff --git a/scripts/automated_build_scripts/opensrc-build.sh b/scripts/automated_build_scripts/opensrc-build.sh
new file mode 100755
index 0000000000..c1b592a972
--- /dev/null
+++ b/scripts/automated_build_scripts/opensrc-build.sh
@@ -0,0 +1,391 @@
+#!/bin/sh
+
+# This is the build script used by Linden Lab's autmated build system.
+#
+
+set -x
+
+export PATH=/bin:/usr/bin:$PATH
+arch=`uname | cut -b-6`
+here=`echo $0 | sed 's:[^/]*$:.:'`
+year=`date +%Y`
+branch=`svn info | grep '^URL:' | sed 's:.*/::'`
+revision=`svn info | grep '^Revision:' | sed 's/.*: //'`
+
+[ x"$WGET_CACHE" = x ] && export WGET_CACHE=/var/tmp/parabuild/wget
+[ x"$S3GET_URL" = x ] && export S3GET_URL=http://viewer-source-downloads.s3.amazonaws.com/$year
+[ x"$S3PUT_URL" = x ] && export S3PUT_URL=https://s3.amazonaws.com/viewer-source-downloads/$year
+[ x"$PUBLIC_URL" = x ] && export PUBLIC_URL=http://secondlife.com/developers/opensource/downloads/$year
+[ x"$PUBLIC_EMAIL" = x ] && export PUBLIC_EMAIL=sldev-commits@lists.secondlife.com
+
+# Make sure command worked and bail out if not, reporting failure to parabuild
+fail()
+{
+ release_lock
+ echo "BUILD FAILED" $@
+ exit 1
+}
+
+pass()
+{
+ release_lock
+ echo "BUILD SUCCESSFUL"
+ exit 0
+}
+
+# Locking to avoid contention with u-s-c
+LOCK_CREATE=/usr/bin/lockfile-create
+LOCK_TOUCH=/usr/bin/lockfile-touch
+LOCK_REMOVE=/usr/bin/lockfile-remove
+LOCK_PROCESS=
+
+locking_available()
+{
+ test -x "$LOCK_CREATE"\
+ -a -x "$LOCK_TOUCH"\
+ -a -x "$LOCK_REMOVE"
+}
+
+acquire_lock()
+{
+ if locking_available
+ then
+ if "$LOCK_CREATE" /var/lock/update-system-config --retry 99
+ then
+ "$LOCK_TOUCH" /var/lock/update-system-config &
+ LOCK_PROCESS="$!"
+ else
+ fail acquire lock
+ fi
+ else
+ true
+ fi
+}
+
+release_lock()
+{
+ if locking_available
+ then
+ if test x"$LOCK_PROCESS" != x
+ then
+ kill "$LOCK_PROCESS"
+ "$LOCK_REMOVE" /var/lock/update-system-config
+ else
+ echo No Lock Acquired >&2
+ fi
+ else
+ true
+ fi
+}
+
+get_asset()
+{
+ mkdir -p "$WGET_CACHE" || fail creating WGET_CACHE
+ local tarball=`basename "$1"`
+ test -r "$WGET_CACHE/$tarball" || ( cd "$WGET_CACHE" && curl --location --remote-name "$1" || fail getting $1 )
+ case "$tarball" in
+ *.zip) unzip -qq -d .. -o "$WGET_CACHE/$tarball" || fail unzip $tarball ;;
+ *.tar.gz|*.tgz) tar -C .. -xzf "$WGET_CACHE/$tarball" || fail untar $tarball ;;
+ *) fail unrecognized filetype: $tarball ;;
+ esac
+}
+
+s3_available()
+{
+ test -x "$helpers/s3get.sh" -a -x "$helpers/s3put.sh" -a -r "$helpers/s3curl.pl"
+}
+
+build_dir_Darwin()
+{
+ echo build-darwin-universal
+}
+
+build_dir_Linux()
+{
+ echo viewer-linux-i686-`echo $1 | tr A-Z a-z`
+}
+
+build_dir_CYGWIN()
+{
+ echo build-vc80
+}
+
+installer_Darwin()
+{
+ ls -1td "`build_dir_Darwin Release`/newview/"*.dmg 2>/dev/null | sed 1q
+}
+
+installer_Linux()
+{
+ ls -1td "`build_dir_Linux Release`/newview/"*.tar.bz2 2>/dev/null | sed 1q
+}
+
+installer_CYGWIN()
+{
+ d=`build_dir_CYGWIN Release`
+ p=`sed 's:.*=::' "$d/newview/Release/touched.bat"`
+ echo "$d/newview/Release/$p"
+}
+
+# deal with aborts etc..
+trap fail 1 2 3 14 15
+
+# Check location
+cd "$here/../.."
+
+test -x ../linden/scripts/automated_build_scripts/opensrc-build.sh\
+ || fail 'The parent dir of your checkout needs to be named "linden"'
+
+. doc/asset_urls.txt
+get_asset "$SLASSET_ART"
+
+
+# Set up platform specific stuff
+case "$arch" in
+
+# Note that we can only build the "Release" variant for Darwin, because of a compiler bug:
+# ld: bl out of range (-16777272 max is +/-16M)
+# from __static_initialization_and_destruction_0(int, int)at 0x033D319C
+# in __StaticInit of
+# indra/build-darwin-universal/newview/SecondLife.build/Debug/Second Life.build/Objects-normal/ppc/llvoicevisualizer.o
+# to ___cxa_atexit$island_2 at 0x023D50F8
+# in __text of
+# indra/build-darwin-universal/newview/SecondLife.build/Debug/Second Life.build/Objects-normal/ppc/Second Life
+# in __static_initialization_and_destruction_0(int, int)
+# from indra/build-darwin-universal/newview/SecondLife.build/Debug/Second Life.build/Objects-normal/ppc/llvoicevisualizer.o
+
+Darwin)
+ helpers=/usr/local/buildscripts/generic_vc
+ variants="Release"
+ cmake_generator="Xcode"
+ fmod=fmodapi375mac
+ fmod_tar="$fmod.zip"
+ fmod_so=libfmod.a
+ fmod_lib=lib
+ target_dirs="libraries/universal-darwin/lib_debug
+ libraries/universal-darwin/lib_release
+ libraries/universal-darwin/lib_release_client"
+ other_archs="$S3GET_URL/$branch/$revision/CYGWIN $S3GET_URL/$branch/$revision/Linux"
+ mail="$helpers"/mail.py
+ all_done="$helpers"/all_done.py
+ get_asset "$SLASSET_LIBS_DARWIN"
+ ;;
+
+CYGWIN)
+ helpers=/cygdrive/c/buildscripts
+ variants="Debug RelWithDebInfo Release"
+ #variants="Release"
+ cmake_generator="vc80"
+ fmod=fmodapi375win
+ fmod_tar=fmodapi375win.zip
+ fmod_so=fmodvc.lib
+ fmod_lib=lib
+ target_dirs="libraries/i686-win32/lib/debug
+ libraries/i686-win32/lib/release"
+ other_archs="$S3GET_URL/$branch/$revision/Darwin $S3GET_URL/$branch/$revision/Linux"
+ export PATH="/cygdrive/c/Python25:/cygdrive/c/Program Files/Cmake 2.6/bin":$PATH
+ export PERL="/cygdrive/c/Perl/bin/perl.exe"
+ export S3CURL="C:\\buildscripts\s3curl.pl"
+ export CURL="C:\\cygwin\\bin\\curl.exe"
+ mail="C:\\buildscripts\\mail.py"
+ all_done="C:\\buildscripts\\all_done.py"
+ get_asset "$SLASSET_LIBS_WIN32"
+ ;;
+
+Linux)
+ helpers=/var/opt/parabuild/buildscripts/generic_vc
+ [ x"$CXX" = x ] && test -x /usr/bin/g++-4.1 && export CXX=/usr/bin/g++-4.1
+ acquire_lock
+ variants="Debug RelWithDebInfo Release"
+ #variants="Release"
+ cmake_generator="Unix Makefiles"
+ fmod=fmodapi375linux
+ fmod_tar="$fmod".tar.gz
+ fmod_so=libfmod-3.75.so
+ fmod_lib=.
+ target_dirs="libraries/i686-linux/lib_debug
+ libraries/i686-linux/lib_release
+ libraries/i686-linux/lib_release_client"
+ other_archs="$S3GET_URL/$branch/$revision/Darwin $S3GET_URL/$branch/$revision/CYGWIN"
+ mail="$helpers"/mail.py
+ all_done="$helpers"/all_done.py
+ # Change the DISTCC_DIR to be somewhere that the parabuild process can write to
+ if test -r /etc/debian_version
+ then
+ [ x"$DISTCC_DIR" = x ] && export DISTCC_DIR=/var/tmp/parabuild
+ case `cat /etc/debian_version` in
+ 3.*) [ x"$DISTCC_HOSTS" = x ]\
+ && export DISTCC_HOSTS="build-linux-1
+ build-linux-2
+ build-linux-3
+ build-linux-4
+ build-linux-5" ;;
+ 4.*) [ x"$DISTCC_HOSTS" = x ]\
+ && export DISTCC_HOSTS="build-linux-6
+ build-linux-7
+ build-linux-8
+ build-linux-9" ;;
+ esac
+ fi
+
+ get_asset "$SLASSET_LIBS_LINUXI386"
+ ;;
+
+*) fail undefined $arch ;;
+esac
+
+get_asset "http://www.fmod.org/index.php/release/version/$fmod_tar"
+
+# Special case for Mac...
+case "$arch" in
+
+Darwin)
+ if lipo -create -output "../$fmod"/api/$fmod_lib/libfmod-universal.a\
+ "../$fmod"/api/$fmod_lib/libfmod.a\
+ "../$fmod"/api/$fmod_lib/libfmodx86.a
+ then
+ mv "../$fmod"/api/$fmod_lib/libfmod.a "../$fmod"/api/$fmod_lib/libfmodppc.a
+ mv "../$fmod"/api/$fmod_lib/libfmod-universal.a "../$fmod"/api/$fmod_lib/libfmod.a
+ echo Created fat binary
+ else
+ fail running lipo
+ fi
+ ;;
+
+esac
+
+# ensure helpers are up to date
+( cd "$helpers" && svn up )
+
+# First, go into the directory where the code was checked out by Parabuild
+cd indra
+
+# This is the way it works now, but it will soon work on a variant dependent way
+for target_dir in $target_dirs
+do
+ mkdir -p "../$target_dir"
+ cp -f "../../$fmod/api/$fmod_lib/$fmod_so" "../$target_dir"
+done
+mkdir -p "../libraries/include"
+cp -f "../../$fmod/api/inc/"* "../libraries/include"
+
+# Special Windows case
+test -r "../../$fmod/api/fmod.dll" && cp -f "../../$fmod/api/fmod.dll" newview
+
+# Now run the build command over all variants
+succeeded=true
+cp /dev/null build.log
+
+### TEST CODE - remove when done
+### variants=
+### echo "Artificial build failure to test notifications" > build.log
+### succeeded=false
+### END TEST CODE
+
+for variant in $variants
+do
+ build_dir=`build_dir_$arch $variant`
+ rm -rf "$build_dir"
+ # This is the way it will work in future
+ #for target_dir in $target_dirs
+ #do
+ # mkdir -p "$build_dir/$target_dir"
+ # cp "../../$fmod/api/$fmod_lib/$fmod_so" "$build_dir/$target_dir"
+ #done
+ #mkdir -p "$build_dir/libraries/include"
+ #cp "../../$fmod/api/inc/"* "$build_dir/libraries/include"
+ echo "==== $variant ====" >> build.log
+ if ./develop.py \
+ --unattended \
+ --incredibuild \
+ -t $variant \
+ -G "$cmake_generator" \
+ configure \
+ -DPACKAGE:BOOL=ON >>build.log 2>&1
+ then
+ if ./develop.py\
+ --unattended\
+ --incredibuild \
+ -t $variant\
+ -G "$cmake_generator" \
+ build package >>build.log 2>&1
+ then
+ # run tests if needed
+ true
+ else
+ succeeded=false
+ fi
+ else
+ succeeded=false
+ fi
+done
+
+# check statuis and upload results to S3
+subject=
+if $succeeded
+then
+ package=`installer_$arch`
+ test -r "$package" || fail not found: $package
+ package_file=`echo $package | sed 's:.*/::'`
+ if s3_available
+ then
+ echo "$PUBLIC_URL/$branch/$revision/$package_file" > "$arch"
+ echo "$PUBLIC_URL/$branch/$revision/good-build.$arch" >> "$arch"
+ "$helpers/s3put.sh" "$package" "$S3PUT_URL/$branch/$revision/$package_file" binary/octet-stream\
+ || fail Uploading "$package"
+ "$helpers/s3put.sh" build.log "$S3PUT_URL/$branch/$revision/good-build.$arch" text/plain\
+ || fail Uploading build.log
+ "$helpers/s3put.sh" "$arch" "$S3PUT_URL/$branch/$revision/$arch" text/plain\
+ || fail Uploading token file
+ if python "$all_done"\
+ curl\
+ "$S3GET_URL/$branch/$revision/$arch"\
+ $other_archs > message
+ then
+ subject="Successful Build for $branch ($revision)"
+ fi
+ else
+ true s3 is not available
+ fi
+else
+ if s3_available
+ then
+ "$helpers/s3put.sh" build.log "$S3PUT_URL/$branch/$revision/failed-build.$arch" text/plain\
+ || fail Uploading build.log
+ subject="Failed Build for $branch ($revision) on $arch"
+ cat >message <<EOF
+Build for $branch ($revision) failed for $arch.
+Please see the build log for details:
+
+$PUBLIC_URL/$branch/$revision/failed-build.$arch
+
+EOF
+ else
+ true s3 is not available
+ fi
+fi
+
+# We have something to say...
+if [ x"$subject" != x ]
+then
+ # Extract change list since last build
+ if [ x"$PARABUILD_CHANGE_LIST_NUMBER" = x ]
+ then
+ echo "No change information available" >> message
+ elif [ x"$PARABUILD_PREVIOUS_CHANGE_LIST_NUMBER" = x ]
+ then
+ ( cd .. && svn log --verbose --stop-on-copy --limit 50 ) >>message
+ else
+ ( cd .. && svn log --verbose -r"$PARABUILD_PREVIOUS_CHANGE_LIST_NUMBER":"$PARABUILD_CHANGE_LIST_NUMBER" ) >>message
+ fi
+ # $PUBLIC_EMAIL can be a list, so no quotes
+ python "$mail" "$subject" $PUBLIC_EMAIL <message
+fi
+
+if $succeeded
+then
+ pass
+else
+ fail
+fi
+
diff --git a/scripts/build_version.py b/scripts/build_version.py
new file mode 100755
index 0000000000..4bef290b7d
--- /dev/null
+++ b/scripts/build_version.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Print the build information embedded in a header file.
+#
+# Expects to be invoked from the command line with a file name and a
+# list of directories to search. The file name will be one of the
+# following:
+#
+# llversionserver.h
+# llversionviewer.h
+#
+# The directory list that follows will include indra/llcommon, where
+# these files live.
+
+import errno, os, re
+
+def get_version(filename):
+ fp = open(filename)
+ data = fp.read()
+ fp.close()
+
+ vals = {}
+ m = re.search('const S32 LL_VERSION_MAJOR = (\d+);', data)
+ vals['major'] = m.group(1)
+ m = re.search('const S32 LL_VERSION_MINOR = (\d+);', data)
+ vals['minor'] = m.group(1)
+ m = re.search('const S32 LL_VERSION_PATCH = (\d+);', data)
+ vals['patch'] = m.group(1)
+ m = re.search('const S32 LL_VERSION_BUILD = (\d+);', data)
+ vals['build'] = m.group(1)
+
+ return "%(major)s.%(minor)s.%(patch)s.%(build)s" % vals
+
+if __name__ == '__main__':
+ import sys
+
+ try:
+ for path in sys.argv[2:]:
+ name = os.path.join(path, sys.argv[1])
+ try:
+ print get_version(name)
+ break
+ except OSError, err:
+ if err.errno != errno.ENOENT:
+ raise
+ else:
+ print >> sys.stderr, 'File not found:', sys.argv[1]
+ sys.exit(1)
+ except AttributeError:
+ print >> sys.stderr, 'Error: malformatted file: ', name
+ sys.exit(1)
+ except IndexError:
+ print >> sys.stderr, ('Usage: %s llversion[...].h [directories]' %
+ sys.argv[0])
diff --git a/scripts/install.py b/scripts/install.py
new file mode 100755
index 0000000000..c2adf4d0a2
--- /dev/null
+++ b/scripts/install.py
@@ -0,0 +1,1150 @@
+#!/usr/bin/env python
+"""\
+@file install.py
+@author Phoenix
+@date 2008-01-27
+@brief Install files into an indra checkout.
+
+Install files as specified by:
+https://wiki.lindenlab.com/wiki/User:Phoenix/Library_Installation
+
+
+$LicenseInfo:firstyear=2007&license=mit$
+
+Copyright (c) 2007-2009, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+import sys
+import os.path
+
+# Look for indra/lib/python in all possible parent directories ...
+# This is an improvement over the setup-path.py method used previously:
+# * the script may blocated anywhere inside the source tree
+# * it doesn't depend on the current directory
+# * it doesn't depend on another file being present.
+
+def add_indra_lib_path():
+ root = os.path.realpath(__file__)
+ # always insert the directory of the script in the search path
+ dir = os.path.dirname(root)
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+
+ # Now go look for indra/lib/python in the parent dies
+ while root != os.path.sep:
+ root = os.path.dirname(root)
+ dir = os.path.join(root, 'indra', 'lib', 'python')
+ if os.path.isdir(dir):
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+ return root
+ else:
+ print >>sys.stderr, "This script is not inside a valid installation."
+ sys.exit(1)
+
+base_dir = add_indra_lib_path()
+
+import copy
+import optparse
+import os
+import platform
+import pprint
+import shutil
+import tarfile
+import tempfile
+import urllib2
+import urlparse
+
+try:
+ # Python 2.6
+ from hashlib import md5
+except ImportError:
+ # Python 2.5 and earlier
+ from md5 import new as md5
+
+from indra.base import llsd
+from indra.util import helpformatter
+
+class InstallFile(object):
+ "This is just a handy way to throw around details on a file in memory."
+ def __init__(self, pkgname, url, md5sum, cache_dir, platform_path):
+ self.pkgname = pkgname
+ self.url = url
+ self.md5sum = md5sum
+ filename = urlparse.urlparse(url)[2].split('/')[-1]
+ self.filename = os.path.join(cache_dir, filename)
+ self.platform_path = platform_path
+
+ def __str__(self):
+ return "ifile{%s:%s}" % (self.pkgname, self.url)
+
+ def _is_md5sum_match(self):
+ hasher = md5(file(self.filename, 'rb').read())
+ if hasher.hexdigest() == self.md5sum:
+ return True
+ return False
+
+ def is_match(self, platform):
+ """@brief Test to see if this ifile is part of platform
+ @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
+ @return Returns True if the ifile is in the platform.
+ """
+ if self.platform_path[0] == 'common':
+ return True
+ req_platform_path = platform.split('/')
+ #print "platform:",req_platform_path
+ #print "path:",self.platform_path
+ # to match, every path part much match
+ match_count = min(len(req_platform_path), len(self.platform_path))
+ for ii in range(0, match_count):
+ if req_platform_path[ii] != self.platform_path[ii]:
+ return False
+ #print "match!"
+ return True
+
+ def fetch_local(self):
+ #print "Looking for:",self.filename
+ if not os.path.exists(self.filename):
+ pass
+ elif self.md5sum and not self._is_md5sum_match():
+ print "md5 mismatch:", self.filename
+ os.remove(self.filename)
+ else:
+ print "Found matching package:", self.filename
+ return
+ print "Downloading",self.url,"to local file",self.filename
+ file(self.filename, 'wb').write(urllib2.urlopen(self.url).read())
+ if self.md5sum and not self._is_md5sum_match():
+ raise RuntimeError("Error matching md5 for %s" % self.url)
+
+class LicenseDefinition(object):
+ def __init__(self, definition):
+ #probably looks like:
+ # { text : ...,
+ # url : ...
+ # blessed : ...
+ # }
+ self._definition = definition
+
+
+class InstallableDefinition(object):
+ def __init__(self, definition):
+ #probably looks like:
+ # { packages : {platform...},
+ # copyright : ...
+ # license : ...
+ # description: ...
+ # }
+ self._definition = definition
+
+ def _ifiles_from(self, tree, pkgname, cache_dir):
+ return self._ifiles_from_path(tree, pkgname, cache_dir, [])
+
+ def _ifiles_from_path(self, tree, pkgname, cache_dir, path):
+ ifiles = []
+ if 'url' in tree:
+ ifiles.append(InstallFile(
+ pkgname,
+ tree['url'],
+ tree.get('md5sum', None),
+ cache_dir,
+ path))
+ else:
+ for key in tree:
+ platform_path = copy.copy(path)
+ platform_path.append(key)
+ ifiles.extend(
+ self._ifiles_from_path(
+ tree[key],
+ pkgname,
+ cache_dir,
+ platform_path))
+ return ifiles
+
+ def ifiles(self, pkgname, platform, cache_dir):
+ """@brief return a list of appropriate InstallFile instances to install
+ @param pkgname The name of the package to be installed, eg 'tut'
+ @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
+ @param cache_dir The directory to cache downloads.
+ @return Returns a list of InstallFiles which are part of this install
+ """
+ if 'packages' not in self._definition:
+ return []
+ all_ifiles = self._ifiles_from(
+ self._definition['packages'],
+ pkgname,
+ cache_dir)
+ if platform == 'all':
+ return all_ifiles
+ #print "Considering", len(all_ifiles), "packages for", pkgname
+ # split into 2 lines because pychecker thinks it might return none.
+ files = [ifile for ifile in all_ifiles if ifile.is_match(platform)]
+ return files
+
+class InstalledPackage(object):
+ def __init__(self, definition):
+ # looks like:
+ # { url1 : { files: [file1,file2,...], md5sum:... },
+ # url2 : { files: [file1,file2,...], md5sum:... },...
+ # }
+ self._installed = {}
+ for url in definition:
+ self._installed[url] = definition[url]
+
+ def urls(self):
+ return self._installed.keys()
+
+ def files_in(self, url):
+ return self._installed[url].get('files', [])
+
+ def get_md5sum(self, url):
+ return self._installed[url].get('md5sum', None)
+
+ def remove(self, url):
+ self._installed.pop(url)
+
+ def add_files(self, url, files):
+ if url not in self._installed:
+ self._installed[url] = {}
+ self._installed[url]['files'] = files
+
+ def set_md5sum(self, url, md5sum):
+ if url not in self._installed:
+ self._installed[url] = {}
+ self._installed[url]['md5sum'] = md5sum
+
+class Installer(object):
+ def __init__(self, install_filename, installed_filename, dryrun):
+ self._install_filename = install_filename
+ self._install_changed = False
+ self._installed_filename = installed_filename
+ self._installed_changed = False
+ self._dryrun = dryrun
+ self._installables = {}
+ self._licenses = {}
+ self._installed = {}
+ self.load()
+
+ def load(self):
+ if os.path.exists(self._install_filename):
+ install = llsd.parse(file(self._install_filename, 'rb').read())
+ try:
+ for name in install['installables']:
+ self._installables[name] = InstallableDefinition(
+ install['installables'][name])
+ except KeyError:
+ pass
+ try:
+ for name in install['licenses']:
+ self._licenses[name] = LicenseDefinition(install['licenses'][name])
+ except KeyError:
+ pass
+ if os.path.exists(self._installed_filename):
+ installed = llsd.parse(file(self._installed_filename, 'rb').read())
+ try:
+ bins = installed['installables']
+ for name in bins:
+ self._installed[name] = InstalledPackage(bins[name])
+ except KeyError:
+ pass
+
+ def _write(self, filename, state):
+ print "Writing state to",filename
+ if not self._dryrun:
+ file(filename, 'wb').write(llsd.format_pretty_xml(state))
+
+ def save(self):
+ if self._install_changed:
+ state = {}
+ state['licenses'] = {}
+ for name in self._licenses:
+ state['licenses'][name] = self._licenses[name]._definition
+ #print "self._installables:",self._installables
+ state['installables'] = {}
+ for name in self._installables:
+ state['installables'][name] = \
+ self._installables[name]._definition
+ self._write(self._install_filename, state)
+ if self._installed_changed:
+ state = {}
+ state['installables'] = {}
+ bin = state['installables']
+ for name in self._installed:
+ #print "installed:",name,self._installed[name]._installed
+ bin[name] = self._installed[name]._installed
+ self._write(self._installed_filename, state)
+
+ def is_valid_license(self, bin):
+ "@brief retrun true if we have valid license info for installable."
+ installable = self._installables[bin]._definition
+ if 'license' not in installable:
+ print >>sys.stderr, "No license info found for", bin
+ print >>sys.stderr, 'Please add the license with the',
+ print >>sys.stderr, '--add-installable option. See', \
+ sys.argv[0], '--help'
+ return False
+ if installable['license'] not in self._licenses:
+ lic = installable['license']
+ print >>sys.stderr, "Missing license info for '" + lic + "'.",
+ print >>sys.stderr, 'Please add the license with the',
+ print >>sys.stderr, '--add-license option. See', sys.argv[0],
+ print >>sys.stderr, '--help'
+ return False
+ return True
+
+ def list_installables(self):
+ "Return a list of all known installables."
+ return sorted(self._installables.keys())
+
+ def detail_installable(self, name):
+ "Return a installable definition detail"
+ return self._installables[name]._definition
+
+ def list_licenses(self):
+ "Return a list of all known licenses."
+ return sorted(self._licenses.keys())
+
+ def detail_license(self, name):
+ "Return a license definition detail"
+ return self._licenses[name]._definition
+
+ def list_installed(self):
+ "Return a list of installed packages."
+ return sorted(self._installed.keys())
+
+ def detail_installed(self, name):
+ "Return file list for specific installed package."
+ filelist = []
+ for url in self._installed[name]._installed.keys():
+ filelist.extend(self._installed[name].files_in(url))
+ return filelist
+
+ def _update_field(self, description, field, value, multiline=False):
+ """Given a block and a field name, add or update it.
+ @param description a dict containing all the details of a description.
+ @param field the name of the field to update.
+ @param value the value of the field to update; if omitted, interview
+ will ask for value.
+ @param multiline boolean specifying whether field is multiline or not.
+ """
+ if value:
+ description[field] = value
+ else:
+ if field in description:
+ print "Update value for '" + field + "'"
+ print "(Leave blank to keep current value)"
+ print "Current Value: '" + description[field] + "'"
+ else:
+ print "Specify value for '" + field + "'"
+ if not multiline:
+ new_value = raw_input("Enter New Value: ")
+ else:
+ print "Please enter " + field + ". End input with EOF (^D)."
+ new_value = sys.stdin.read()
+
+ if field in description and not new_value:
+ pass
+ elif new_value:
+ description[field] = new_value
+
+ self._install_changed = True
+ return True
+
+ def _update_installable(self, name, platform, url, md5sum):
+ """Update installable entry with specific package information.
+ @param installable[in,out] a dict containing installable details.
+ @param platform Platform info, i.e. linux/i686, windows/i686 etc.
+ @param url URL of tar file
+ @param md5sum md5sum of tar file
+ """
+ installable = self._installables[name]._definition
+ path = platform.split('/')
+ if 'packages' not in installable:
+ installable['packages'] = {}
+ update = installable['packages']
+ for child in path:
+ if child not in update:
+ update[child] = {}
+ parent = update
+ update = update[child]
+ parent[child]['url'] = llsd.uri(url)
+ parent[child]['md5sum'] = md5sum
+
+ self._install_changed = True
+ return True
+
+
+ def add_installable_package(self, name, **kwargs):
+ """Add an url for a platform path to the installable.
+ @param installable[in,out] a dict containing installable details.
+ """
+ platform_help_str = """\
+Please enter a new package location and url. Some examples:
+common -- specify a package for all platforms
+linux -- specify a package for all arch and compilers on linux
+darwin/universal -- specify a mac os x universal
+windows/i686/vs/2003 -- specify a windows visual studio 2003 package"""
+ if name not in self._installables:
+ print "Error: must add library with --add-installable or " \
+ +"--add-installable-metadata before using " \
+ +"--add-installable-package option"
+ return False
+ else:
+ print "Updating installable '" + name + "'."
+ for arg in ('platform', 'url', 'md5sum'):
+ if not kwargs[arg]:
+ if arg == 'platform':
+ print platform_help_str
+ kwargs[arg] = raw_input("Package "+arg+":")
+ #path = kwargs['platform'].split('/')
+
+ return self._update_installable(name, kwargs['platform'],
+ kwargs['url'], kwargs['md5sum'])
+
+ def add_installable_metadata(self, name, **kwargs):
+ """Interactively add (only) library metadata into install,
+ w/o adding installable"""
+ if name not in self._installables:
+ print "Adding installable '" + name + "'."
+ self._installables[name] = InstallableDefinition({})
+ else:
+ print "Updating installable '" + name + "'."
+ installable = self._installables[name]._definition
+ for field in ('copyright', 'license', 'description'):
+ self._update_field(installable, field, kwargs[field])
+ print "Added installable '" + name + "':"
+ pprint.pprint(self._installables[name])
+
+ return True
+
+ def add_installable(self, name, **kwargs):
+ "Interactively pull a new installable into the install"
+ ret_a = self.add_installable_metadata(name, **kwargs)
+ ret_b = self.add_installable_package(name, **kwargs)
+ return (ret_a and ret_b)
+
+ def remove_installable(self, name):
+ self._installables.pop(name)
+ self._install_changed = True
+
+ def add_license(self, name, **kwargs):
+ if name not in self._licenses:
+ print "Adding license '" + name + "'."
+ self._licenses[name] = LicenseDefinition({})
+ else:
+ print "Updating license '" + name + "'."
+ the_license = self._licenses[name]._definition
+ for field in ('url', 'text'):
+ multiline = False
+ if field == 'text':
+ multiline = True
+ self._update_field(the_license, field, kwargs[field], multiline)
+ self._install_changed = True
+ return True
+
+ def remove_license(self, name):
+ self._licenses.pop(name)
+ self._install_changed = True
+
+ def _uninstall(self, installables):
+ """@brief Do the actual removal of files work.
+ *NOTE: This method is not transactionally safe -- ie, if it
+ raises an exception, internal state may be inconsistent. How
+ should we address this?
+ @param installables The package names to remove
+ """
+ remove_file_list = []
+ for pkgname in installables:
+ for url in self._installed[pkgname].urls():
+ remove_file_list.extend(
+ self._installed[pkgname].files_in(url))
+ self._installed[pkgname].remove(url)
+ if not self._dryrun:
+ self._installed_changed = True
+ if not self._dryrun:
+ self._installed.pop(pkgname)
+ remove_dir_set = set()
+ for filename in remove_file_list:
+ print "rm",filename
+ if not self._dryrun:
+ if os.path.exists(filename):
+ remove_dir_set.add(os.path.dirname(filename))
+ try:
+ os.remove(filename)
+ except OSError:
+ # This is just for cleanup, so we don't care
+ # about normal failures.
+ pass
+ for dirname in remove_dir_set:
+ try:
+ os.removedirs(dirname)
+ except OSError:
+ # This is just for cleanup, so we don't care about
+ # normal failures.
+ pass
+
+ def uninstall(self, installables, install_dir):
+ """@brief Remove the packages specified.
+ @param installables The package names to remove
+ @param install_dir The directory to work from
+ """
+ print "uninstall",installables,"from",install_dir
+ cwd = os.getcwdu()
+ os.chdir(install_dir)
+ try:
+ self._uninstall(installables)
+ finally:
+ os.chdir(cwd)
+
+ def _build_ifiles(self, platform, cache_dir):
+ """@brief determine what files to install
+ @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
+ @param cache_dir The directory to cache downloads.
+ @return Returns the ifiles to install
+ """
+ ifiles = []
+ for bin in self._installables:
+ ifiles.extend(self._installables[bin].ifiles(bin,
+ platform,
+ cache_dir))
+ to_install = []
+ #print "self._installed",self._installed
+ for ifile in ifiles:
+ if ifile.pkgname not in self._installed:
+ to_install.append(ifile)
+ elif ifile.url not in self._installed[ifile.pkgname].urls():
+ to_install.append(ifile)
+ elif ifile.md5sum != \
+ self._installed[ifile.pkgname].get_md5sum(ifile.url):
+ # *TODO: We may want to uninstall the old version too
+ # when we detect it is installed, but the md5 sum is
+ # different.
+ to_install.append(ifile)
+ else:
+ #print "Installation up to date:",
+ # ifile.pkgname,ifile.platform_path
+ pass
+ #print "to_install",to_install
+ return to_install
+
+ def _install(self, to_install, install_dir):
+ for ifile in to_install:
+ tar = tarfile.open(ifile.filename, 'r')
+ print "Extracting",ifile.filename,"to",install_dir
+ if not self._dryrun:
+ # *NOTE: try to call extractall, which first appears
+ # in python 2.5. Phoenix 2008-01-28
+ try:
+ tar.extractall(path=install_dir)
+ except AttributeError:
+ _extractall(tar, path=install_dir)
+ if ifile.pkgname in self._installed:
+ self._installed[ifile.pkgname].add_files(
+ ifile.url,
+ tar.getnames())
+ self._installed[ifile.pkgname].set_md5sum(
+ ifile.url,
+ ifile.md5sum)
+ else:
+ # *HACK: this understands the installed package syntax.
+ definition = { ifile.url :
+ {'files': tar.getnames(),
+ 'md5sum' : ifile.md5sum } }
+ self._installed[ifile.pkgname] = InstalledPackage(definition)
+ self._installed_changed = True
+
+ def install(self, installables, platform, install_dir, cache_dir):
+ """@brief Do the installation for for the platform.
+ @param installables The requested installables to install.
+ @param platform The target platform. Eg, windows or linux/i686/gcc/3.3
+ @param install_dir The root directory to install into. Created
+ if missing.
+ @param cache_dir The directory to cache downloads. Created if
+ missing.
+ """
+ # The ordering of steps in the method is to help reduce the
+ # likelihood that we break something.
+ install_dir = os.path.realpath(install_dir)
+ cache_dir = os.path.realpath(cache_dir)
+ _mkdir(install_dir)
+ _mkdir(cache_dir)
+ to_install = self._build_ifiles(platform, cache_dir)
+
+ # Filter for files which we actually requested to install.
+ to_install = [ifl for ifl in to_install if ifl.pkgname in installables]
+ for ifile in to_install:
+ ifile.fetch_local()
+ self._install(to_install, install_dir)
+
+ def do_install(self, installables, platform, install_dir, cache_dir=None,
+ check_license=True, scp=None):
+ """Determine what installables should be installed. If they were
+ passed in on the command line, use them, otherwise install
+ all known installables.
+ """
+ if not cache_dir:
+ cache_dir = _default_installable_cache()
+ all_installables = self.list_installables()
+ if not len(installables):
+ install_installables = all_installables
+ else:
+ # passed in on the command line. We'll need to verify we
+ # know about them here.
+ install_installables = installables
+ for installable in install_installables:
+ if installable not in all_installables:
+ raise RuntimeError('Unknown installable: %s' %
+ (installable,))
+ if check_license:
+ # *TODO: check against a list of 'known good' licenses.
+ # *TODO: check for urls which conflict -- will lead to
+ # problems.
+ for installable in install_installables:
+ if not self.is_valid_license(installable):
+ return 1
+
+ # Set up the 'scp' handler
+ opener = urllib2.build_opener()
+ scp_or_http = SCPOrHTTPHandler(scp)
+ opener.add_handler(scp_or_http)
+ urllib2.install_opener(opener)
+
+ # Do the work of installing the requested installables.
+ self.install(
+ install_installables,
+ platform,
+ install_dir,
+ cache_dir)
+ scp_or_http.cleanup()
+
+ def do_uninstall(self, installables, install_dir):
+ # Do not bother to check license if we're uninstalling.
+ all_installed = self.list_installed()
+ if not len(installables):
+ uninstall_installables = all_installed
+ else:
+ # passed in on the command line. We'll need to verify we
+ # know about them here.
+ uninstall_installables = installables
+ for installable in uninstall_installables:
+ if installable not in all_installed:
+ raise RuntimeError('Installable not installed: %s' %
+ (installable,))
+ self.uninstall(uninstall_installables, install_dir)
+
+class SCPOrHTTPHandler(urllib2.BaseHandler):
+ """Evil hack to allow both the build system and developers consume
+ proprietary binaries.
+ To use http, export the environment variable:
+ INSTALL_USE_HTTP_FOR_SCP=true
+ """
+ def __init__(self, scp_binary):
+ self._scp = scp_binary
+ self._dir = None
+
+ def scp_open(self, request):
+ #scp:codex.lindenlab.com:/local/share/install_pkgs/package.tar.bz2
+ remote = request.get_full_url()[4:]
+ if os.getenv('INSTALL_USE_HTTP_FOR_SCP', None) == 'true':
+ return self.do_http(remote)
+ try:
+ return self.do_scp(remote)
+ except:
+ self.cleanup()
+ raise
+
+ def do_http(self, remote):
+ url = remote.split(':',1)
+ if not url[1].startswith('/'):
+ # in case it's in a homedir or something
+ url.insert(1, '/')
+ url.insert(0, "http://")
+ url = ''.join(url)
+ print "Using HTTP:",url
+ return urllib2.urlopen(url)
+
+ def do_scp(self, remote):
+ if not self._dir:
+ self._dir = tempfile.mkdtemp()
+ local = os.path.join(self._dir, remote.split('/')[-1:][0])
+ command = []
+ for part in (self._scp, remote, local):
+ if ' ' in part:
+ # I hate shell escaping.
+ part.replace('\\', '\\\\')
+ part.replace('"', '\\"')
+ command.append('"%s"' % part)
+ else:
+ command.append(part)
+ #print "forking:", command
+ rv = os.system(' '.join(command))
+ if rv != 0:
+ raise RuntimeError("Cannot fetch: %s" % remote)
+ return file(local, 'rb')
+
+ def cleanup(self):
+ if self._dir:
+ shutil.rmtree(self._dir)
+
+
+#
+# *NOTE: PULLED FROM PYTHON 2.5 tarfile.py Phoenix 2008-01-28
+#
+def _extractall(tar, path=".", members=None):
+ """Extract all members from the archive to the current working
+ directory and set owner, modification time and permissions on
+ directories afterwards. `path' specifies a different directory
+ to extract to. `members' is optional and must be a subset of the
+ list returned by getmembers().
+ """
+ directories = []
+
+ if members is None:
+ members = tar
+
+ for tarinfo in members:
+ if tarinfo.isdir():
+ # Extract directory with a safe mode, so that
+ # all files below can be extracted as well.
+ try:
+ os.makedirs(os.path.join(path, tarinfo.name), 0777)
+ except EnvironmentError:
+ pass
+ directories.append(tarinfo)
+ else:
+ tar.extract(tarinfo, path)
+
+ # Reverse sort directories.
+ directories.sort(lambda a, b: cmp(a.name, b.name))
+ directories.reverse()
+
+ # Set correct owner, mtime and filemode on directories.
+ for tarinfo in directories:
+ path = os.path.join(path, tarinfo.name)
+ try:
+ tar.chown(tarinfo, path)
+ tar.utime(tarinfo, path)
+ tar.chmod(tarinfo, path)
+ except tarfile.ExtractError, e:
+ if tar.errorlevel > 1:
+ raise
+ else:
+ tar._dbg(1, "tarfile: %s" % e)
+
+
+def _mkdir(directory):
+ "Safe, repeatable way to make a directory."
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+def _get_platform():
+ "Return appropriate platform packages for the environment."
+ platform_map = {
+ 'darwin': 'darwin',
+ 'linux2': 'linux',
+ 'win32' : 'windows',
+ 'cygwin' : 'windows',
+ 'solaris' : 'solaris'
+ }
+ this_platform = platform_map[sys.platform]
+ if this_platform == 'linux':
+ if platform.architecture()[0] == '64bit':
+ # TODO -- someday when install.py accepts a platform of the form
+ # os/arch/compiler/compiler_version then we can replace the
+ # 'linux64' platform with 'linux/x86_64/gcc/4.1'
+ this_platform = 'linux'
+ return this_platform
+
+def _getuser():
+ "Get the user"
+ try:
+ # Unix-only.
+ import getpass
+ return getpass.getuser()
+ except ImportError:
+ import ctypes
+ MAX_PATH = 260 # according to a recent WinDef.h
+ name = ctypes.create_unicode_buffer(MAX_PATH)
+ namelen = ctypes.c_int(len(name)) # len in chars, NOT bytes
+ if not ctypes.windll.advapi32.GetUserNameW(name, ctypes.byref(namelen)):
+ raise ctypes.WinError()
+ return name.value
+
+def _default_installable_cache():
+ """In general, the installable files do not change much, so find a
+ host/user specific location to cache files."""
+ user = _getuser()
+ cache_dir = "/var/tmp/%s/install.cache" % user
+ if _get_platform() == 'windows':
+ cache_dir = os.path.join(tempfile.gettempdir(), \
+ 'install.cache.%s' % user)
+ return cache_dir
+
+def parse_args():
+ parser = optparse.OptionParser(
+ usage="usage: %prog [options] [installable1 [installable2...]]",
+ formatter = helpformatter.Formatter(),
+ description="""This script fetches and installs installable packages.
+It also handles uninstalling those packages and manages the mapping between
+packages and their license.
+
+The process is to open and read an install manifest file which specifies
+what files should be installed. For each installable to be installed.
+ * make sure it has a license
+ * check the installed version
+ ** if not installed and needs to be, download and install
+ ** if installed version differs, download & install
+
+If no installables are specified on the command line, then the defaut
+behavior is to install all known installables appropriate for the platform
+specified or uninstall all installables if --uninstall is set. You can specify
+more than one installable on the command line.
+
+When specifying a platform, you can specify 'all' to install all
+packages, or any platform of the form:
+
+OS[/arch[/compiler[/compiler_version]]]
+
+Where the supported values for each are:
+OS: darwin, linux, windows, solaris
+arch: i686, x86_64, ppc, universal
+compiler: vs, gcc
+compiler_version: 2003, 2005, 2008, 3.3, 3.4, 4.0, etc.
+
+No checks are made to ensure a valid combination of platform
+parts. Some exmples of valid platforms:
+
+windows
+windows/i686/vs/2005
+linux/x86_64/gcc/3.3
+linux/x86_64/gcc/4.0
+darwin/universal/gcc/4.0
+""")
+ parser.add_option(
+ '--dry-run',
+ action='store_true',
+ default=False,
+ dest='dryrun',
+ help='Do not actually install files. Downloads will still happen.')
+ parser.add_option(
+ '--install-manifest',
+ type='string',
+ default=os.path.join(base_dir, 'install.xml'),
+ dest='install_filename',
+ help='The file used to describe what should be installed.')
+ parser.add_option(
+ '--installed-manifest',
+ type='string',
+ default=os.path.join(base_dir, 'installed.xml'),
+ dest='installed_filename',
+ help='The file used to record what is installed.')
+ parser.add_option(
+ '--export-manifest',
+ action='store_true',
+ default=False,
+ dest='export_manifest',
+ help="Print the install manifest to stdout and exit.")
+ parser.add_option(
+ '-p', '--platform',
+ type='string',
+ default=_get_platform(),
+ dest='platform',
+ help="""Override the automatically determined platform. \
+You can specify 'all' to do a installation of installables for all platforms.""")
+ parser.add_option(
+ '--cache-dir',
+ type='string',
+ default=_default_installable_cache(),
+ dest='cache_dir',
+ help='Where to download files. Default: %s'% \
+ (_default_installable_cache()))
+ parser.add_option(
+ '--install-dir',
+ type='string',
+ default=base_dir,
+ dest='install_dir',
+ help='Where to unpack the installed files.')
+ parser.add_option(
+ '--list-installed',
+ action='store_true',
+ default=False,
+ dest='list_installed',
+ help="List the installed package names and exit.")
+ parser.add_option(
+ '--skip-license-check',
+ action='store_false',
+ default=True,
+ dest='check_license',
+ help="Do not perform the license check.")
+ parser.add_option(
+ '--list-licenses',
+ action='store_true',
+ default=False,
+ dest='list_licenses',
+ help="List known licenses and exit.")
+ parser.add_option(
+ '--detail-license',
+ type='string',
+ default=None,
+ dest='detail_license',
+ help="Get detailed information on specified license and exit.")
+ parser.add_option(
+ '--add-license',
+ type='string',
+ default=None,
+ dest='new_license',
+ help="""Add a license to the install file. Argument is the name of \
+license. Specify --license-url if the license is remote or specify \
+--license-text, otherwse the license text will be read from standard \
+input.""")
+ parser.add_option(
+ '--license-url',
+ type='string',
+ default=None,
+ dest='license_url',
+ help="""Put the specified url into an added license. \
+Ignored if --add-license is not specified.""")
+ parser.add_option(
+ '--license-text',
+ type='string',
+ default=None,
+ dest='license_text',
+ help="""Put the text into an added license. \
+Ignored if --add-license is not specified.""")
+ parser.add_option(
+ '--remove-license',
+ type='string',
+ default=None,
+ dest='remove_license',
+ help="Remove a named license.")
+ parser.add_option(
+ '--remove-installable',
+ type='string',
+ default=None,
+ dest='remove_installable',
+ help="Remove a installable from the install file.")
+ parser.add_option(
+ '--add-installable',
+ type='string',
+ default=None,
+ dest='add_installable',
+ help="""Add a installable into the install file. Argument is \
+the name of the installable to add.""")
+ parser.add_option(
+ '--add-installable-metadata',
+ type='string',
+ default=None,
+ dest='add_installable_metadata',
+ help="""Add package for library into the install file. Argument is \
+the name of the library to add.""")
+ parser.add_option(
+ '--installable-copyright',
+ type='string',
+ default=None,
+ dest='installable_copyright',
+ help="""Copyright for specified new package. Ignored if \
+--add-installable is not specified.""")
+ parser.add_option(
+ '--installable-license',
+ type='string',
+ default=None,
+ dest='installable_license',
+ help="""Name of license for specified new package. Ignored if \
+--add-installable is not specified.""")
+ parser.add_option(
+ '--installable-description',
+ type='string',
+ default=None,
+ dest='installable_description',
+ help="""Description for specified new package. Ignored if \
+--add-installable is not specified.""")
+ parser.add_option(
+ '--add-installable-package',
+ type='string',
+ default=None,
+ dest='add_installable_package',
+ help="""Add package for library into the install file. Argument is \
+the name of the library to add.""")
+ parser.add_option(
+ '--package-platform',
+ type='string',
+ default=None,
+ dest='package_platform',
+ help="""Platform for specified new package. \
+Ignored if --add-installable or --add-installable-package is not specified.""")
+ parser.add_option(
+ '--package-url',
+ type='string',
+ default=None,
+ dest='package_url',
+ help="""URL for specified package. \
+Ignored if --add-installable or --add-installable-package is not specified.""")
+ parser.add_option(
+ '--package-md5',
+ type='string',
+ default=None,
+ dest='package_md5',
+ help="""md5sum for new package. \
+Ignored if --add-installable or --add-installable-package is not specified.""")
+ parser.add_option(
+ '--list',
+ action='store_true',
+ default=False,
+ dest='list_installables',
+ help="List the installables in the install manifest and exit.")
+ parser.add_option(
+ '--detail',
+ type='string',
+ default=None,
+ dest='detail_installable',
+ help="Get detailed information on specified installable and exit.")
+ parser.add_option(
+ '--detail-installed',
+ type='string',
+ default=None,
+ dest='detail_installed',
+ help="Get list of files for specified installed installable and exit.")
+ parser.add_option(
+ '--uninstall',
+ action='store_true',
+ default=False,
+ dest='uninstall',
+ help="""Remove the installables specified in the arguments. Just like \
+during installation, if no installables are listed then all installed \
+installables are removed.""")
+ parser.add_option(
+ '--scp',
+ type='string',
+ default='scp',
+ dest='scp',
+ help="Specify the path to your scp program.")
+
+ return parser.parse_args()
+
+def main():
+ options, args = parse_args()
+ installer = Installer(
+ options.install_filename,
+ options.installed_filename,
+ options.dryrun)
+
+ #
+ # Handle the queries for information
+ #
+ if options.list_installed:
+ print "installed list:", installer.list_installed()
+ return 0
+ if options.list_installables:
+ print "installable list:", installer.list_installables()
+ return 0
+ if options.detail_installable:
+ try:
+ detail = installer.detail_installable(options.detail_installable)
+ print "Detail on installable",options.detail_installable+":"
+ pprint.pprint(detail)
+ except KeyError:
+ print "Installable '"+options.detail_installable+"' not found in",
+ print "install file."
+ return 0
+ if options.detail_installed:
+ try:
+ detail = installer.detail_installed(options.detail_installed)
+ #print "Detail on installed",options.detail_installed+":"
+ for line in detail:
+ print line
+ except:
+ raise
+ print "Installable '"+options.detail_installed+"' not found in ",
+ print "install file."
+ return 0
+ if options.list_licenses:
+ print "license list:", installer.list_licenses()
+ return 0
+ if options.detail_license:
+ try:
+ detail = installer.detail_license(options.detail_license)
+ print "Detail on license",options.detail_license+":"
+ pprint.pprint(detail)
+ except KeyError:
+ print "License '"+options.detail_license+"' not defined in",
+ print "install file."
+ return 0
+ if options.export_manifest:
+ # *HACK: just re-parse the install manifest and pretty print
+ # it. easier than looking at the datastructure designed for
+ # actually determining what to install
+ install = llsd.parse(file(options.install_filename, 'rb').read())
+ pprint.pprint(install)
+ return 0
+
+ #
+ # Handle updates -- can only do one of these
+ # *TODO: should this change the command line syntax?
+ #
+ if options.new_license:
+ if not installer.add_license(
+ options.new_license,
+ text=options.license_text,
+ url=options.license_url):
+ return 1
+ elif options.remove_license:
+ installer.remove_license(options.remove_license)
+ elif options.remove_installable:
+ installer.remove_installable(options.remove_installable)
+ elif options.add_installable:
+ if not installer.add_installable(
+ options.add_installable,
+ copyright=options.installable_copyright,
+ license=options.installable_license,
+ description=options.installable_description,
+ platform=options.package_platform,
+ url=options.package_url,
+ md5sum=options.package_md5):
+ return 1
+ elif options.add_installable_metadata:
+ if not installer.add_installable_metadata(
+ options.add_installable_metadata,
+ copyright=options.installable_copyright,
+ license=options.installable_license,
+ description=options.installable_description):
+ return 1
+ elif options.add_installable_package:
+ if not installer.add_installable_package(
+ options.add_installable_package,
+ platform=options.package_platform,
+ url=options.package_url,
+ md5sum=options.package_md5):
+ return 1
+ elif options.uninstall:
+ installer.do_uninstall(args, options.install_dir)
+ else:
+ installer.do_install(args, options.platform, options.install_dir,
+ options.cache_dir, options.check_license,
+ options.scp)
+
+ # save out any changes
+ installer.save()
+ return 0
+
+if __name__ == '__main__':
+ #print sys.argv
+ sys.exit(main())
diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg
index b9c694bbbf..77dc940335 100644
--- a/scripts/messages/message_template.msg
+++ b/scripts/messages/message_template.msg
@@ -53,7 +53,7 @@ version 2.0
// OpenCircuit - Tells the recipient's messaging system to open the descibed circuit
{
- OpenCircuit Fixed 0xFFFFFFFC NotTrusted Unencoded
+ OpenCircuit Fixed 0xFFFFFFFC NotTrusted Unencoded UDPBlackListed
{
CircuitInfo Single
{ IP IPADDR }
@@ -594,7 +594,7 @@ version 2.0
// global x,y,z. Otherwise, use center of the AABB.
// reliable
{
- PlacesReply Low 30 Trusted Zerocoded
+ PlacesReply Low 30 Trusted Zerocoded UDPDeprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -619,6 +619,7 @@ version 2.0
{ SnapshotID LLUUID }
{ Dwell F32 }
{ Price S32 }
+ //{ ProductSKU Variable 1 }
}
}
@@ -723,6 +724,10 @@ version 2.0
{ Auction BOOL }
{ Dwell F32 }
}
+ {
+ StatusData Variable
+ { Status U32 }
+ }
}
// DirPeopleReply
@@ -767,6 +772,10 @@ version 2.0
{ UnixTime U32 }
{ EventFlags U32 }
}
+ {
+ StatusData Variable
+ { Status U32 }
+ }
}
// DirGroupsReply
@@ -852,6 +861,10 @@ version 2.0
{ ExpirationDate U32 }
{ PriceForListing S32 }
}
+ {
+ StatusData Variable
+ { Status U32 }
+ }
}
@@ -1036,7 +1049,7 @@ version 2.0
// dataserver -> simulator -> viewer
// reliable
{
- DirLandReply Low 50 Trusted Zerocoded
+ DirLandReply Low 50 Trusted Zerocoded UDPDeprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -1053,14 +1066,15 @@ version 2.0
{ ForSale BOOL }
{ SalePrice S32 }
{ ActualArea S32 }
+ //{ ProductSKU Variable 1 }
}
}
-// DirPopularQuery viewer->sim
+// DEPRECATED: DirPopularQuery viewer->sim
// Special query for the land for sale/auction panel.
// reliable
{
- DirPopularQuery Low 51 NotTrusted Zerocoded
+ DirPopularQuery Low 51 NotTrusted Zerocoded Deprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -1073,11 +1087,11 @@ version 2.0
}
}
-// DirPopularQueryBackend sim->dataserver
+// DEPRECATED: DirPopularQueryBackend sim->dataserver
// Special query for the land for sale/auction panel.
// reliable
{
- DirPopularQueryBackend Low 52 Trusted Zerocoded
+ DirPopularQueryBackend Low 52 Trusted Zerocoded Deprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -1091,11 +1105,11 @@ version 2.0
}
}
-// DirPopularReply
+// DEPRECATED: DirPopularReply
// dataserver -> simulator -> viewer
// reliable
{
- DirPopularReply Low 53 Trusted Zerocoded
+ DirPopularReply Low 53 Trusted Zerocoded Deprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -1179,7 +1193,7 @@ version 2.0
// simulator -> viewer
// reliable
{
- ParcelObjectOwnersReply Low 57 Trusted Zerocoded
+ ParcelObjectOwnersReply Low 57 Trusted Zerocoded UDPDeprecated
{
Data Variable
{ OwnerID LLUUID }
@@ -1369,6 +1383,10 @@ version 2.0
{ AgentID LLUUID }
{ KickedFromEstateID U32 }
}
+ {
+ AgentInfo Single
+ { AgentEffectiveMaturity U32 }
+ }
}
// DataHomeLocationReply data->sim
@@ -1389,7 +1407,7 @@ version 2.0
// called when all of the information has been collected and readied for
// the agent.
{
- TeleportFinish Low 69 Trusted Unencoded
+ TeleportFinish Low 69 Trusted Unencoded UDPBlackListed
{
Info Single
{ AgentID LLUUID }
@@ -1465,7 +1483,7 @@ version 2.0
}
}
-// TeleportFailed somehwere->sim->viewer
+// TeleportFailed somewhere->sim->viewer
// announce failure of teleport request
{
TeleportFailed Low 74 Trusted Unencoded
@@ -1474,6 +1492,11 @@ version 2.0
{ AgentID LLUUID }
{ Reason Variable 1 } // string
}
+ {
+ AlertInfo Variable
+ { Message Variable 1 } // string id
+ { ExtraParams Variable 1 } // llsd extra parameters
+ }
}
@@ -1955,10 +1978,19 @@ version 2.0
}
-// ObjectPosition
-// viewer -> simulator
+// DEPRECATED: ObjectPosition
+// == Old Behavior ==
+// Set the position on objects
+//
+// == Reason for deprecation ==
+// Unused code path was removed in the move to Havok4
+// Object position, scale and rotation messages were already unified
+// to MultipleObjectUpdate and this message was unused cruft.
+//
+// == New Location ==
+// MultipleObjectUpdate can be used instead.
{
- ObjectPosition Medium 4 NotTrusted Zerocoded
+ ObjectPosition Medium 4 NotTrusted Zerocoded Deprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -1972,10 +2004,19 @@ version 2.0
}
-// ObjectScale
-// viewer -> simulator
+// DEPRECATED: ObjectScale
+// == Old Behavior ==
+// Set the scale on objects
+//
+// == Reason for deprecation ==
+// Unused code path was removed in the move to Havok4
+// Object position, scale and rotation messages were already unified
+// to MultipleObjectUpdate and this message was unused cruft.
+//
+// == New Location ==
+// MultipleObjectUpdate can be used instead.
{
- ObjectScale Low 92 NotTrusted Zerocoded
+ ObjectScale Low 92 NotTrusted Zerocoded Deprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -2418,6 +2459,15 @@ version 2.0
{ LocalID U32 }
{ GrabOffset LLVector3 }
}
+ {
+ SurfaceInfo Variable
+ { UVCoord LLVector3 }
+ { STCoord LLVector3 }
+ { FaceIndex S32 }
+ { Position LLVector3 }
+ { Normal LLVector3 }
+ { Binormal LLVector3 }
+ }
}
@@ -2439,6 +2489,16 @@ version 2.0
{ GrabPosition LLVector3 } // LLVector3, region local
{ TimeSinceLast U32 }
}
+ {
+ SurfaceInfo Variable
+ { UVCoord LLVector3 }
+ { STCoord LLVector3 }
+ { FaceIndex S32 }
+ { Position LLVector3 }
+ { Normal LLVector3 }
+ { Binormal LLVector3 }
+ }
+
}
@@ -2454,6 +2514,15 @@ version 2.0
ObjectData Single
{ LocalID U32 }
}
+ {
+ SurfaceInfo Variable
+ { UVCoord LLVector3 }
+ { STCoord LLVector3 }
+ { FaceIndex S32 }
+ { Position LLVector3 }
+ { Normal LLVector3 }
+ { Binormal LLVector3 }
+ }
}
@@ -2543,6 +2612,10 @@ version 2.0
{ East F32 }
{ North F32 }
}
+ {
+ ModifyBlockExtended Variable
+ { BrushSize F32 }
+ }
}
@@ -2651,7 +2724,7 @@ version 2.0
// end viewer to simulator section
{
- ViewerStats Low 131 NotTrusted Zerocoded
+ ViewerStats Low 131 NotTrusted Zerocoded UDPDeprecated
{
AgentData Single
{ AgentID LLUUID }
@@ -2760,6 +2833,11 @@ version 2.0
AlertData Single
{ Message Variable 1 }
}
+ {
+ AlertInfo Variable
+ { Message Variable 1 }
+ { ExtraParams Variable 1 }
+ }
}
// Send an AlertMessage to the named agent.
@@ -2848,6 +2926,10 @@ version 2.0
{ StatID U32 }
{ StatValue F32 }
}
+ {
+ PidStat Single
+ { PID S32 }
+ }
}
// viewer -> sim
@@ -2892,6 +2974,14 @@ version 2.0
{ UseEstateSun BOOL }
{ SunHour F32 } // last value set by estate or region controls JC
}
+ {
+ RegionInfo2 Single
+ { ProductSKU Variable 1 } // string
+ { ProductName Variable 1 } // string
+ { MaxAgents32 U32 } // Identical to RegionInfo.MaxAgents but allows greater range
+ { HardMaxAgents U32 }
+ { HardMaxObjects U32 }
+ }
}
// GodUpdateRegionInfo
@@ -3009,6 +3099,14 @@ version 2.0
RegionInfo2 Single
{ RegionID LLUUID }
}
+ {
+ RegionInfo3 Single
+ { CPUClassID S32 }
+ { CPURatio S32 }
+ { ColoName Variable 1 } // string
+ { ProductSKU Variable 1 } // string
+ { ProductName Variable 1 } // string
+ }
}
// RegionHandshakeReply
@@ -3246,7 +3344,7 @@ version 2.0
// CrossedRegion - new way to tell a viewer it has gone across a region
// boundary
{
- CrossedRegion Medium 7 Trusted Unencoded
+ CrossedRegion Medium 7 Trusted Unencoded UDPBlackListed
{
AgentData Single
{ AgentID LLUUID }
@@ -3284,7 +3382,7 @@ version 2.0
// EnableSimulator - Preps a viewer to receive data from a simulator
{
- EnableSimulator Low 151 Trusted Unencoded
+ EnableSimulator Low 151 Trusted Unencoded UDPBlackListed
{
SimulatorInfo Single
{ Handle U64 }
@@ -4817,6 +4915,12 @@ version 2.0
{ ActualArea S32 }
{ Final BOOL } // true if buyer should be in tier
}
+ {
+ RegionData Single // included so region name shows up in transaction logs
+ { RegionID LLUUID }
+ { GridX U32 }
+ { GridY U32 }
+ }
}
// sim ->dataserver
@@ -5183,6 +5287,15 @@ version 2.0
VisualParam Variable
{ ParamValue U8 }
}
+ {
+ AgentAccess Variable
+ { AgentLegacyAccess U8 }
+ { AgentMaxAccess U8 }
+ }
+ {
+ AgentInfo Variable
+ { Flags U32 }
+ }
}
// ChildAgentAlive
@@ -5285,6 +5398,7 @@ version 2.0
{ ObjectID LLUUID }
{ ItemID LLUUID }
{ Running BOOL }
+// { Mono BOOL } Added to LLSD message
}
}
@@ -6698,6 +6812,19 @@ version 2.0
{ SquareMetersCommitted S32 }
{ Description Variable 1 } // string
}
+ // For replies that are part of a transaction (buying something) provide
+ // metadata for localization. If TransactionType is 0, the message is
+ // purely a balance update. Added for server 1.40 and viewer 2.1. JC
+ {
+ TransactionInfo Single
+ { TransactionType S32 } // lltransactiontype.h
+ { SourceID LLUUID }
+ { IsSourceGroup BOOL }
+ { DestID LLUUID }
+ { IsDestGroup BOOL }
+ { Amount S32 }
+ { ItemDescription Variable 1 } // string
+ }
}
@@ -6724,6 +6851,17 @@ version 2.0
{ SquareMetersCommitted S32 }
{ Description Variable 1 } // string
}
+ // See MoneyBalanceReply above.
+ {
+ TransactionInfo Single
+ { TransactionType S32 } // lltransactiontype.h
+ { SourceID LLUUID }
+ { IsSourceGroup BOOL }
+ { DestID LLUUID }
+ { IsDestGroup BOOL }
+ { Amount S32 }
+ { ItemDescription Variable 1 } // string
+ }
}
@@ -8650,7 +8788,7 @@ version 2.0
// spaceserver -> simulator
{
- RpcScriptRequestInboundForward Low 416 Trusted Unencoded
+ RpcScriptRequestInboundForward Low 416 Trusted Unencoded UDPDeprecated
{
DataBlock Single
{ RPCServerIP IPADDR }
@@ -8745,7 +8883,7 @@ version 2.0
// LandStatReply
// Sent by the simulator in response to LandStatRequest
{
- LandStatReply Low 422 Trusted Unencoded
+ LandStatReply Low 422 Trusted Unencoded UDPDeprecated
{
RequestData Single
{ ReportType U32 }
@@ -8802,3 +8940,63 @@ version 2.0
{ IncludeInSearch BOOL }
}
}
+
+
+// This message is sent from viewer -> simulator when the viewer wants
+// to rez an object out of inventory back to its position before it
+// last moved into the inventory
+{
+ RezRestoreToWorld Low 425 NotTrusted Unencoded UDPDeprecated
+ {
+ AgentData Single
+ { AgentID LLUUID }
+ { SessionID LLUUID }
+ }
+ {
+ InventoryData Single
+ { ItemID LLUUID }
+ { FolderID LLUUID }
+ { CreatorID LLUUID } // permissions
+ { OwnerID LLUUID } // permissions
+ { GroupID LLUUID } // permissions
+ { BaseMask U32 } // permissions
+ { OwnerMask U32 } // permissions
+ { GroupMask U32 } // permissions
+ { EveryoneMask U32 } // permissions
+ { NextOwnerMask U32 } // permissions
+ { GroupOwned BOOL } // permissions
+ { TransactionID LLUUID }
+ { Type S8 }
+ { InvType S8 }
+ { Flags U32 }
+ { SaleType U8 }
+ { SalePrice S32 }
+ { Name Variable 1 }
+ { Description Variable 1 }
+ { CreationDate S32 }
+ { CRC U32 }
+ }
+}
+
+// Link inventory
+{
+ LinkInventoryItem Low 426 NotTrusted Zerocoded
+ {
+ AgentData Single
+ { AgentID LLUUID }
+ { SessionID LLUUID }
+ }
+ {
+ InventoryBlock Single
+ { CallbackID U32 } // Async Response
+ { FolderID LLUUID }
+ { TransactionID LLUUID } // Going to become TransactionID
+ { OldItemID LLUUID }
+ { Type S8 }
+ { InvType S8 }
+ { Name Variable 1 }
+ { Description Variable 1 }
+
+ }
+}
+
diff --git a/scripts/setup-path.py b/scripts/setup-path.py
index fcd713c655..55e0f1a85f 100755..100644
--- a/scripts/setup-path.py
+++ b/scripts/setup-path.py
@@ -4,30 +4,25 @@
@brief Get the python library directory in the path, so we don't have
to screw with PYTHONPATH or symbolic links.
-$LicenseInfo:firstyear=2007&license=viewergpl$
+$LicenseInfo:firstyear=2007&license=viewerlgpl$
+Second Life Viewer Source Code
+Copyright (C) 2010, Linden Research, Inc.
-Copyright (c) 2007, Linden Research, Inc.
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation;
+version 2.1 of the License only.
-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.
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
diff --git a/scripts/template_verifier.py b/scripts/template_verifier.py
index a7dbda9e3b..ddb050fbbb 100755..100644
--- a/scripts/template_verifier.py
+++ b/scripts/template_verifier.py
@@ -3,30 +3,25 @@
@file template_verifier.py
@brief Message template compatibility verifier.
-$LicenseInfo:firstyear=2007&license=viewergpl$
+$LicenseInfo:firstyear=2007&license=viewerlgpl$
+Second Life Viewer Source Code
+Copyright (C) 2010, Linden Research, Inc.
-Copyright (c) 2007, Linden Research, Inc.
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation;
+version 2.1 of the License only.
-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.
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
@@ -38,13 +33,38 @@ If [FILE] [FILE] is specified, two local files will be checked against
each other.
"""
-from os.path import realpath, dirname, join, exists
-setup_path = join(dirname(realpath(__file__)), "setup-path.py")
-if exists(setup_path):
- execfile(setup_path)
+import sys
+import os.path
+
+# Look for indra/lib/python in all possible parent directories ...
+# This is an improvement over the setup-path.py method used previously:
+# * the script may blocated anywhere inside the source tree
+# * it doesn't depend on the current directory
+# * it doesn't depend on another file being present.
+
+def add_indra_lib_path():
+ root = os.path.realpath(__file__)
+ # always insert the directory of the script in the search path
+ dir = os.path.dirname(root)
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+
+ # Now go look for indra/lib/python in the parent dies
+ while root != os.path.sep:
+ root = os.path.dirname(root)
+ dir = os.path.join(root, 'indra', 'lib', 'python')
+ if os.path.isdir(dir):
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+ break
+ else:
+ print >>sys.stderr, "This script is not inside a valid installation."
+ sys.exit(1)
+
+add_indra_lib_path()
+
import optparse
import os
-import sys
import urllib
from indra.ipc import compatibility
@@ -77,7 +97,7 @@ MESSAGE_TEMPLATE = 'message_template.msg'
PRODUCTION_ACCEPTABLE = (compatibility.Same, compatibility.Newer)
DEVELOPMENT_ACCEPTABLE = (
compatibility.Same, compatibility.Newer,
- compatibility.Older, compatibility.Mixed)
+ compatibility.Older, compatibility.Mixed)
MAX_MASTER_AGE = 60 * 60 * 4 # refresh master cache every 4 hours
@@ -177,8 +197,13 @@ def getuser():
import getpass
return getpass.getuser()
except ImportError:
- import win32api
- return win32api.GetUserName()
+ import ctypes
+ MAX_PATH = 260 # according to a recent WinDef.h
+ name = ctypes.create_unicode_buffer(MAX_PATH)
+ namelen = ctypes.c_int(len(name)) # len in chars, NOT bytes
+ if not ctypes.windll.advapi32.GetUserNameW(name, ctypes.byref(namelen)):
+ raise ctypes.WinError()
+ return name.value
def local_master_cache_filename():
"""Returns the location of the master template cache (which is in the system tempdir)
diff --git a/scripts/update_version_files.py b/scripts/update_version_files.py
index 077120127b..da60fd105a 100755
--- a/scripts/update_version_files.py
+++ b/scripts/update_version_files.py
@@ -4,11 +4,37 @@
# instead of having to figure it out by hand
#
-from os.path import realpath, dirname, join, exists
-setup_path = join(dirname(realpath(__file__)), "setup-path.py")
-if exists(setup_path):
- execfile(setup_path)
-import getopt, sys, os, re, commands
+import sys
+import os.path
+
+# Look for indra/lib/python in all possible parent directories ...
+# This is an improvement over the setup-path.py method used previously:
+# * the script may blocated anywhere inside the source tree
+# * it doesn't depend on the current directory
+# * it doesn't depend on another file being present.
+
+def add_indra_lib_path():
+ root = os.path.realpath(__file__)
+ # always insert the directory of the script in the search path
+ dir = os.path.dirname(root)
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+
+ # Now go look for indra/lib/python in the parent dies
+ while root != os.path.sep:
+ root = os.path.dirname(root)
+ dir = os.path.join(root, 'indra', 'lib', 'python')
+ if os.path.isdir(dir):
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+ break
+ else:
+ print >>sys.stderr, "This script is not inside a valid installation."
+ sys.exit(1)
+
+add_indra_lib_path()
+
+import getopt, os, re, commands
from indra.util import llversion
svn = os.path.expandvars("${SVN}")
@@ -21,6 +47,9 @@ def usage():
Options:
--version
Specify the version string to replace current version.
+ --revision
+ Specify the revision to replace the last digit of the version.
+ By default, revision is computed from the version control system.
--skip-on-branch
Specify a regular expression against which the current branch
is matched. If it matches, then leave version strings alone.
@@ -100,7 +129,7 @@ re_map['indra/llcommon/llversionserver.h'] = \
'const S32 LL_VERSION_BUILD = %(SERVER_VER_BUILD)s;'),
('const char \* const LL_CHANNEL = "(.+)";',
'const char * const LL_CHANNEL = "%(SERVER_CHANNEL)s";'))
-re_map['indra/newview/res/newViewRes.rc'] = \
+re_map['indra/newview/res/viewerRes.rc'] = \
(('FILEVERSION [0-9,]+',
'FILEVERSION %(VER_MAJOR)s,%(VER_MINOR)s,%(VER_PATCH)s,%(VER_BUILD)s'),
('PRODUCTVERSION [0-9,]+',
@@ -135,6 +164,7 @@ def main():
opts, args = getopt.getopt(sys.argv[1:],
"",
['version=',
+ 'revision=',
'channel=',
'server_channel=',
'skip-on-branch=',
@@ -145,12 +175,15 @@ def main():
update_server = False
update_viewer = False
new_version = None
+ new_revision = None
new_viewer_channel = None
new_server_channel = None
skip_on_branch_re = None
for o,a in opts:
if o in ('--version'):
new_version = a
+ if o in ('--revision'):
+ new_revision = a
if o in ('--skip-on-branch'):
skip_on_branch_re = re.compile(a)
if o in ('--channel'):
@@ -215,23 +248,26 @@ def main():
if update_server:
server_version = new_version
else:
- # Assume we're updating just the build number
- cl = '%s info "%s"' % (svn, src_root)
- status, output = _getstatusoutput(cl)
- if verbose:
- print
- print "svn info output:"
- print "----------------"
- print output
-
- branch_match = svn_branch_re.search(output)
- revision_match = svn_revision_re.search(output)
- if not branch_match or not revision_match:
- print "Failed to execute svn info, output follows:"
- print output
+
+ if llversion.using_svn():
+ if new_revision:
+ revision = new_revision
+ else:
+ revision = llversion.get_svn_revision()
+ branch = llversion.get_svn_branch()
+ elif llversion.using_hg():
+ if new_revision:
+ revision = new_revision
+ else:
+ revision = llversion.get_hg_changeset()
+ branch = llversion.get_hg_repo()
+ elif new_revision:
+ revision = new_revision
+ branch = "unknown"
+ else:
+ print >>sys.stderr, "ERROR: could not determine revision and branch"
return -1
- branch = branch_match.group(1)
- revision = revision_match.group(1)
+
if skip_on_branch_re and skip_on_branch_re.match(branch):
print "Release Candidate Build, leaving version files untouched."
return 0