diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/automated_build_scripts/opensrc-build.sh | 391 | ||||
-rwxr-xr-x | scripts/build_version.py | 54 | ||||
-rwxr-xr-x | scripts/install.py | 1150 | ||||
-rw-r--r-- | scripts/messages/message_template.msg | 244 | ||||
-rw-r--r--[-rwxr-xr-x] | scripts/setup-path.py | 39 | ||||
-rw-r--r--[-rwxr-xr-x] | scripts/template_verifier.py | 85 | ||||
-rwxr-xr-x | scripts/update_version_files.py | 80 |
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 |