template_verifier.py
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:11k
源码类别:

游戏引擎

开发平台:

C++ Builder

  1. #!/usr/bin/python
  2. """
  3. @file template_verifier.py
  4. @brief Message template compatibility verifier.
  5. $LicenseInfo:firstyear=2007&license=viewergpl$
  6. Copyright (c) 2007-2010, Linden Research, Inc.
  7. Second Life Viewer Source Code
  8. The source code in this file ("Source Code") is provided by Linden Lab
  9. to you under the terms of the GNU General Public License, version 2.0
  10. ("GPL"), unless you have obtained a separate licensing agreement
  11. ("Other License"), formally executed by you and Linden Lab.  Terms of
  12. the GPL can be found in doc/GPL-license.txt in this distribution, or
  13. online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  14. There are special exceptions to the terms and conditions of the GPL as
  15. it is applied to this Source Code. View the full text of the exception
  16. in the file doc/FLOSS-exception.txt in this software distribution, or
  17. online at
  18. http://secondlifegrid.net/programs/open_source/licensing/flossexception
  19. By copying, modifying or distributing this software, you acknowledge
  20. that you have read and understood your obligations described above,
  21. and agree to abide by those obligations.
  22. ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  23. WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  24. COMPLETENESS OR PERFORMANCE.
  25. $/LicenseInfo$
  26. """
  27. """template_verifier is a script which will compare the
  28. current repository message template with the "master" message template, accessible
  29. via http://secondlife.com/app/message_template/master_message_template.msg
  30. If [FILE] is specified, it will be checked against the master template.
  31. If [FILE] [FILE] is specified, two local files will be checked against
  32. each other.
  33. """
  34. import sys
  35. import os.path
  36. # Look for indra/lib/python in all possible parent directories ...
  37. # This is an improvement over the setup-path.py method used previously:
  38. #  * the script may blocated anywhere inside the source tree
  39. #  * it doesn't depend on the current directory
  40. #  * it doesn't depend on another file being present.
  41. def add_indra_lib_path():
  42.     root = os.path.realpath(__file__)
  43.     # always insert the directory of the script in the search path
  44.     dir = os.path.dirname(root)
  45.     if dir not in sys.path:
  46.         sys.path.insert(0, dir)
  47.     # Now go look for indra/lib/python in the parent dies
  48.     while root != os.path.sep:
  49.         root = os.path.dirname(root)
  50.         dir = os.path.join(root, 'indra', 'lib', 'python')
  51.         if os.path.isdir(dir):
  52.             if dir not in sys.path:
  53.                 sys.path.insert(0, dir)
  54.             break
  55.     else:
  56.         print >>sys.stderr, "This script is not inside a valid installation."
  57.         sys.exit(1)
  58. add_indra_lib_path()
  59. import optparse
  60. import os
  61. import urllib
  62. from indra.ipc import compatibility
  63. from indra.ipc import tokenstream
  64. from indra.ipc import llmessage
  65. def getstatusall(command):
  66.     """ Like commands.getstatusoutput, but returns stdout and 
  67.     stderr separately(to get around "killed by signal 15" getting 
  68.     included as part of the file).  Also, works on Windows."""
  69.     (input, out, err) = os.popen3(command, 't')
  70.     status = input.close() # send no input to the command
  71.     output = out.read()
  72.     error = err.read()
  73.     status = out.close()
  74.     status = err.close() # the status comes from the *last* pipe that is closed
  75.     return status, output, error
  76. def getstatusoutput(command):
  77.     status, output, error = getstatusall(command)
  78.     return status, output
  79. def die(msg):
  80.     print >>sys.stderr, msg
  81.     sys.exit(1)
  82. MESSAGE_TEMPLATE = 'message_template.msg'
  83. PRODUCTION_ACCEPTABLE = (compatibility.Same, compatibility.Newer)
  84. DEVELOPMENT_ACCEPTABLE = (
  85.     compatibility.Same, compatibility.Newer,
  86.     compatibility.Older, compatibility.Mixed)
  87. MAX_MASTER_AGE = 60 * 60 * 4   # refresh master cache every 4 hours
  88. def retry(times, function, *args, **kwargs):
  89.     for i in range(times):
  90.         try:
  91.             return function(*args, **kwargs)
  92.         except Exception, e:
  93.             if i == times - 1:
  94.                 raise e  # we retried all the times we could
  95. def compare(base_parsed, current_parsed, mode):
  96.     """Compare the current template against the base template using the given
  97.     'mode' strictness:
  98.     development: Allows Same, Newer, Older, and Mixed
  99.     production: Allows only Same or Newer
  100.     Print out information about whether the current template is compatible
  101.     with the base template.
  102.     Returns a tuple of (bool, Compatibility)
  103.     Return True if they are compatible in this mode, False if not.
  104.     """
  105.     compat = current_parsed.compatibleWithBase(base_parsed)
  106.     if mode == 'production':
  107.         acceptable = PRODUCTION_ACCEPTABLE
  108.     else:
  109.         acceptable = DEVELOPMENT_ACCEPTABLE
  110.     if type(compat) in acceptable:
  111.         return True, compat
  112.     return False, compat
  113. def fetch(url):
  114.     if url.startswith('file://'):
  115.         # just open the file directly because urllib is dumb about these things
  116.         file_name = url[len('file://'):]
  117.         return open(file_name).read()
  118.     else:
  119.         # *FIX: this doesn't throw an exception for a 404, and oddly enough the sl.com 404 page actually gets parsed successfully
  120.         return ''.join(urllib.urlopen(url).readlines())   
  121. def cache_master(master_url):
  122.     """Using the url for the master, updates the local cache, and returns an url to the local cache."""
  123.     master_cache = local_master_cache_filename()
  124.     master_cache_url = 'file://' + master_cache
  125.     # decide whether to refresh the master cache based on its age
  126.     import time
  127.     if (os.path.exists(master_cache)
  128.         and time.time() - os.path.getmtime(master_cache) < MAX_MASTER_AGE):
  129.         return master_cache_url  # our cache is fresh
  130.     # new master doesn't exist or isn't fresh
  131.     print "Refreshing master cache from %s" % master_url
  132.     def get_and_test_master():
  133.         new_master_contents = fetch(master_url)
  134.         llmessage.parseTemplateString(new_master_contents)
  135.         return new_master_contents
  136.     try:
  137.         new_master_contents = retry(3, get_and_test_master)
  138.     except IOError, e:
  139.         # the refresh failed, so we should just soldier on
  140.         print "WARNING: unable to download new master, probably due to network error.  Your message template compatibility may be suspect."
  141.         print "Cause: %s" % e
  142.         return master_cache_url
  143.     try:
  144.         tmpname = '%s.%d' % (master_cache, os.getpid())
  145.         mc = open(tmpname, 'wb')
  146.         mc.write(new_master_contents)
  147.         mc.close()
  148.         try:
  149.             os.rename(tmpname, master_cache)
  150.         except OSError:
  151.             # We can't rename atomically on top of an existing file on
  152.             # Windows.  Unlinking the existing file will fail if the
  153.             # file is being held open by a process, but there's only
  154.             # so much working around a lame I/O API one can take in
  155.             # a single day.
  156.             os.unlink(master_cache)
  157.             os.rename(tmpname, master_cache)
  158.     except IOError, e:
  159.         print "WARNING: Unable to write master message template to %s, proceeding without cache." % master_cache
  160.         print "Cause: %s" % e
  161.         return master_url
  162.     return master_cache_url
  163. def local_template_filename():
  164.     """Returns the message template's default location relative to template_verifier.py:
  165.     ./messages/message_template.msg."""
  166.     d = os.path.dirname(os.path.realpath(__file__))
  167.     return os.path.join(d, 'messages', MESSAGE_TEMPLATE)
  168. def getuser():
  169.     try:
  170.         # Unix-only.
  171.         import getpass
  172.         return getpass.getuser()
  173.     except ImportError:
  174.         import ctypes
  175.         MAX_PATH = 260                  # according to a recent WinDef.h
  176.         name = ctypes.create_unicode_buffer(MAX_PATH)
  177.         namelen = ctypes.c_int(len(name)) # len in chars, NOT bytes
  178.         if not ctypes.windll.advapi32.GetUserNameW(name, ctypes.byref(namelen)):
  179.             raise ctypes.WinError()
  180.         return name.value
  181. def local_master_cache_filename():
  182.     """Returns the location of the master template cache (which is in the system tempdir)
  183.     <temp_dir>/master_message_template_cache.msg"""
  184.     import tempfile
  185.     d = tempfile.gettempdir()
  186.     user = getuser()
  187.     return os.path.join(d, 'master_message_template_cache.%s.msg' % user)
  188. def run(sysargs):
  189.     parser = optparse.OptionParser(
  190.         usage="usage: %prog [FILE] [FILE]",
  191.         description=__doc__)
  192.     parser.add_option(
  193.         '-m', '--mode', type='string', dest='mode',
  194.         default='development',
  195.         help="""[development|production] The strictness mode to use
  196. while checking the template; see the wiki page for details about
  197. what is allowed and disallowed by each mode:
  198. http://wiki.secondlife.com/wiki/Template_verifier.py
  199. """)
  200.     parser.add_option(
  201.         '-u', '--master_url', type='string', dest='master_url',
  202.         default='http://secondlife.com/app/message_template/master_message_template.msg',
  203.         help="""The url of the master message template.""")
  204.     parser.add_option(
  205.         '-c', '--cache_master', action='store_true', dest='cache_master',
  206.         default=False,  help="""Set to true to attempt use local cached copy of the master template.""")
  207.     options, args = parser.parse_args(sysargs)
  208.     if options.mode == 'production':
  209.         options.cache_master = False
  210.     # both current and master supplied in positional params
  211.     if len(args) == 2:
  212.         master_filename, current_filename = args
  213.         print "master:", master_filename
  214.         print "current:", current_filename
  215.         master_url = 'file://%s' % master_filename
  216.         current_url = 'file://%s' % current_filename
  217.     # only current supplied in positional param
  218.     elif len(args) == 1:
  219.         master_url = None
  220.         current_filename = args[0]
  221.         print "master:", options.master_url 
  222.         print "current:", current_filename
  223.         current_url = 'file://%s' % current_filename
  224.     # nothing specified, use defaults for everything
  225.     elif len(args) == 0:
  226.         master_url  = None
  227.         current_url = None
  228.     else:
  229.         die("Too many arguments")
  230.     if master_url is None:
  231.         master_url = options.master_url
  232.         
  233.     if current_url is None:
  234.         current_filename = local_template_filename()
  235.         print "master:", options.master_url
  236.         print "current:", current_filename
  237.         current_url = 'file://%s' % current_filename
  238.     # retrieve the contents of the local template and check for syntax
  239.     current = fetch(current_url)
  240.     current_parsed = llmessage.parseTemplateString(current)
  241.     if options.cache_master:
  242.         # optionally return a url to a locally-cached master so we don't hit the network all the time
  243.         master_url = cache_master(master_url)
  244.     def parse_master_url():
  245.         master = fetch(master_url)
  246.         return llmessage.parseTemplateString(master)
  247.     try:
  248.         master_parsed = retry(3, parse_master_url)
  249.     except (IOError, tokenstream.ParseError), e:
  250.         if options.mode == 'production':
  251.             raise e
  252.         else:
  253.             print "WARNING: problems retrieving the master from %s."  % master_url
  254.             print "Syntax-checking the local template ONLY, no compatibility check is being run."
  255.             print "Cause: %snn" % e
  256.             return 0
  257.         
  258.     acceptable, compat = compare(
  259.         master_parsed, current_parsed, options.mode)
  260.     def explain(header, compat):
  261.         print header
  262.         # indent compatibility explanation
  263.         print 'nt'.join(compat.explain().split('n'))
  264.     if acceptable:
  265.         explain("--- PASS ---", compat)
  266.     else:
  267.         explain("*** FAIL ***", compat)
  268.         return 1
  269. if __name__ == '__main__':
  270.     sys.exit(run(sys.argv[1:]))