#1034
[tridge/waf-svn.git/.git] / wscript
diff --git a/wscript b/wscript
index a412b40f56e6d7e9ee1f0ad3ef4728a103b424de..c6b4a61e759dccebed12f58841701e9786c0f5fe 100644 (file)
--- a/wscript
+++ b/wscript
@@ -1,42 +1,89 @@
 #! /usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005, 2006 (ita)
+# Thomas Nagy, 2005-2010
 
 """
-This script is not a good example:
- * it is complicated
- * it does not build anything, it just exits in the init method
+to make a custom waf file use the option --tools
 
-Have a look at demos/cc/wscript instead
-For configuration examples: demos/adv/wscript
-For a project without subdirectory: demos/python/wscript
+To add a tool that does not exist in the folder compat15, pass an absolute path:
+./waf-light --make-waf --tools=compat15,/comp/waf/aba.py --prelude=$'\tfrom waflib.extras import aba\n\taba.foo()'
 """
 
-VERSION="1.4.2"
+
+VERSION="1.6.8"
 APPNAME='waf'
 REVISION=''
-srcdir='.'
-blddir='build'
+
+top = '.'
+out = 'build'
 
 demos = ['cpp', 'qt4', 'tex', 'ocaml', 'kde3', 'adv', 'cc', 'idl', 'docbook', 'xmlwaf', 'gnome']
 zip_types = ['bz2', 'gz']
 
-# exclude these modules
-forbidden = [x+'.py' for x in 'Test Weak'.split()]
+PRELUDE = '\timport waflib.extras.compat15'
+
+#from tokenize import *
+import tokenize
+
+import os, sys, re, io, optparse
+
+from waflib import Utils, Options
+from hashlib import md5
+
+from waflib import Configure
+Configure.autoconfig = 1
 
-from tokenize import *
+def sub_file(fname, lst):
 
-import os, sys, base64, shutil, re, random, StringIO, optparse
-import Params, Utils, Options, Common
-try: from hashlib import md5
-except ImportError: from md5 import md5
+       f = open(fname, 'rU')
+       txt = f.read()
+       f.close()
+
+       for (key, val) in lst:
+               re_pat = re.compile(key, re.M)
+               txt = re_pat.sub(val, txt)
+
+       f = open(fname, 'w')
+       f.write(txt)
+       f.close()
 
-pyFileExp = re.compile(".*\.py$")
+print("------> Executing code from the top-level wscript <-----")
+def init(*k, **kw):
+       if Options.options.setver: # maintainer only (ita)
+               ver = Options.options.setver
+               hexver = Utils.num2ver(ver)
+               hexver = '0x%x'%hexver
+               sub_file('wscript', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))
+               sub_file('waf-light', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))
+
+               pats = []
+               pats.append(('^WAFVERSION=(.*)', 'WAFVERSION="%s"' % ver))
+               pats.append(('^HEXVERSION(.*)', 'HEXVERSION=%s' % hexver))
+
+               try:
+                       rev = k[0].cmd_and_log('svnversion').strip().replace('M', '')
+                       pats.append(('^WAFREVISION(.*)', 'WAFREVISION="%s"' % rev))
+               except:
+                       pass
 
-print "------> Executing code from the top-level wscript <-----"
+               sub_file('waflib/Context.py', pats)
+
+               sys.exit(0)
+       elif Options.options.waf:
+               create_waf()
+               sys.exit(0)
+
+def check(ctx):
+       sys.path.insert(0,'')
+       # some tests clobber g_module. We must preserve it here, otherwise we get an error
+       # about an undefined shutdown function
+       mod = Utils.g_module
+       import test.Test
+       test.Test.run_tests()
+       Utils.g_module = mod
 
 # this function is called before any other for parsing the command-line
-def set_options(opt):
+def options(opt):
 
        # generate waf
        opt.add_option('--make-waf', action='store_true', default=False,
@@ -46,7 +93,7 @@ def set_options(opt):
                help='specify the zip type [Allowed values: %s]' % ' '.join(zip_types), dest='zip')
 
        opt.add_option('--make-batch', action='store_true', default=False,
-               help='creates a waf.bat file that calls the waf script. (this is done automatically on win32 systems)',
+               help='creates a convenience waf.bat file (done automatically on win32 systems)',
                dest='make_batch')
 
        opt.add_option('--yes', action='store_true', default=False,
@@ -55,41 +102,17 @@ def set_options(opt):
 
        # those ones are not too interesting
        opt.add_option('--set-version', default='',
-               help='set the version number for waf releases (for the maintainer)', dest='setver')
+               help='sets the version number for waf releases (for the maintainer)', dest='setver')
 
-       opt.add_option('--strip', action='store_true', default=False,
-               help='Shrink waf (strip docstrings, saves 33kb)',
+       opt.add_option('--strip', action='store_true', default=True,
+               help='shrinks waf (strip docstrings, saves 33kb)',
                dest='strip_comments')
-
-def encodeAscii85(s):
-       out=[]
-       app=out.append
-       v=[(0,16777216L),(1,65536),(2,256),(3,1)]
-       cnt,r = divmod(len(s),4)
-       stop=4*cnt
-       p1,p2=s[0:stop],s[stop:]
-       for i in range(cnt):
-               offset=i*4
-               num=0
-               for (j,mul) in v: num+=mul*ord(p1[offset+j])
-               if num==0: out.append('z')
-               else:
-                       x,e=divmod(num,85)
-                       x,d=divmod(x,85)
-                       x,c=divmod(x,85)
-                       a,b=divmod(x,85)
-                       app(chr(a+33)+chr(b+33)+chr(c+33)+chr(d+33)+chr(e+33))
-       if r>0:
-               while len(p2)<4: p2=p2+'\x00'
-               num=0
-               for (j,mul) in v: num+=mul*ord(p2[j])
-               x,e=divmod(num,85)
-               x,d=divmod(x,85)
-               x,c=divmod(x,85)
-               a,b=divmod(x,85)
-               end=chr(a+33)+chr(b+33)+chr(c+33)+chr(d+33)+chr(e+33)
-               app(end[0:1+r])
-       return ''.join(out)
+       opt.add_option('--nostrip', action='store_false', help='no shrinking',
+               dest='strip_comments')
+       opt.add_option('--tools', action='store', help='Comma-separated 3rd party tools to add, eg: "compat,ocaml" [Default: "compat15"]',
+               dest='add3rdparty', default='compat15')
+       opt.add_option('--prelude', action='store', help='Code to execute before calling waf', dest='prelude', default=PRELUDE)
+       opt.load('python')
 
 def compute_revision():
        global REVISION
@@ -101,7 +124,7 @@ def compute_revision():
                        elif name.endswith('.py'):
                                arg.append(os.path.join(dirname, name))
        sources = []
-       os.path.walk('wafadmin', visit, sources)
+       os.path.walk('waflib', visit, sources)
        sources.sort()
        m = md5()
        for source in sources:
@@ -114,50 +137,16 @@ def compute_revision():
                f.close()
        REVISION = m.hexdigest()
 
-#deco_re = re.compile('def\\s+([a-zA-Z_]+)\\(')
-deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
-def process_decorators(body):
-       "modify the python 2.4 decorators"
-       lst = body.split('\n')
-       accu = []
-       all_deco = []
-       buf = [] # put the decorator lines
-       for line in lst:
-               if line.startswith('@'):
-                       buf.append(line[1:])
-               elif buf:
-                       name = deco_re.sub('\\2', line)
-                       if not name:
-                               raise IOError, "decorator not followed by a function!"+line
-                       for x in buf:
-                               all_deco.append("%s(%s)" % (x, name))
-                       accu.append(line)
-                       buf = []
-               else:
-                       accu.append(line)
-       return "\n".join(accu+all_deco)
-
-def process_imports(body):
-       "add the python 2.3 fixes to the redistributable waf"
-       header = '#! /usr/bin/env python\n# encoding: utf-8'
-       impo = ''
-       deco = ''
-
-       if body.find('set(') > -1:
-               impo += 'import sys\nif sys.hexversion < 0x020400f0: from sets import Set as set'
-
-       return "\n".join([header, impo, body, deco])
-
 def process_tokens(tokens):
        accu = []
-       prev = NEWLINE
+       prev = tokenize.NEWLINE
 
-       accu_deco = []
        indent = 0
        line_buf = []
 
        for (type, token, start, end, line) in tokens:
-               if type == NEWLINE:
+               token = token.replace('\r\n', '\n')
+               if type == tokenize.NEWLINE:
                        if line_buf:
                                accu.append(indent * '\t')
                                ln = "".join(line_buf)
@@ -166,23 +155,23 @@ def process_tokens(tokens):
                                accu.append(ln)
                                accu.append('\n')
                                line_buf = []
-                               prev = NEWLINE
-               elif type == INDENT:
+                               prev = tokenize.NEWLINE
+               elif type == tokenize.INDENT:
                        indent += 1
-               elif type == DEDENT:
+               elif type == tokenize.DEDENT:
                        indent -= 1
-               elif type == NAME:
-                       if prev == NAME or prev == NUMBER: line_buf.append(' ')
+               elif type == tokenize.NAME:
+                       if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
                        line_buf.append(token)
-               elif type == NUMBER:
-                       if prev == NAME or prev == NUMBER: line_buf.append(' ')
+               elif type == tokenize.NUMBER:
+                       if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
                        line_buf.append(token)
-               elif type == STRING:
+               elif type == tokenize.STRING:
                        if not line_buf and token.startswith('"'): pass
                        else: line_buf.append(token)
-               elif type == COMMENT:
+               elif type == tokenize.COMMENT:
                        pass
-               elif type == OP:
+               elif type == tokenize.OP:
                        line_buf.append(token)
                else:
                        if token != "\n": line_buf.append(token)
@@ -193,92 +182,160 @@ def process_tokens(tokens):
        body = "".join(accu)
        return body
 
+deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
+def process_decorators(body):
+       lst = body.split('\n')
+       accu = []
+       all_deco = []
+       buf = [] # put the decorator lines
+       for line in lst:
+               if line.startswith('@'):
+                       buf.append(line[1:])
+               elif buf:
+                       name = deco_re.sub('\\2', line)
+                       if not name:
+                               raise IOError("decorator not followed by a function!" + line)
+                       for x in buf:
+                               all_deco.append("%s(%s)" % (x, name))
+                       accu.append(line)
+                       buf = []
+               else:
+                       accu.append(line)
+       return "\n".join(accu+all_deco)
+
 def sfilter(path):
-       f = open(path, "r")
-       if Params.g_options.strip_comments:
-               cnt = process_tokens(generate_tokens(f.readline))
+       if sys.version_info[0] >= 3 and Options.options.strip_comments:
+               f = open(path, "rb")
+               tk = tokenize.tokenize(f.readline)
+               next(tk) # the first one is always tokenize.ENCODING for Python 3, ignore it
+               cnt = process_tokens(tk)
+       elif Options.options.strip_comments and path.endswith('.py'):
+               f = open(path, "r")
+               cnt = process_tokens(tokenize.generate_tokens(f.readline))
        else:
+               f = open(path, "r")
                cnt = f.read()
+
        f.close()
+       if path.endswith('.py') :
+               cnt = process_decorators(cnt)
 
-       cnt = process_decorators(cnt)
-       cnt = process_imports(cnt)
-       if path.endswith('Scripting.py'):
-               cnt = cnt.replace('Utils.python_24_guard()', '')
+               if cnt.find('set(') > -1:
+                       cnt = 'import sys\nif sys.hexversion < 0x020400f0: from sets import Set as set\n' + cnt
+               cnt = '#! /usr/bin/env python\n# encoding: utf-8\n# WARNING! Do not edit! http://waf.googlecode.com/svn/docs/wafbook/single.html#_obtaining_the_waf_file\n\n' + cnt
 
-       return (StringIO.StringIO(cnt), len(cnt), cnt)
+       return (io.BytesIO(cnt.encode('utf-8')), len(cnt), cnt)
 
-def create_waf():
-       print "-> preparing waf"
+def create_waf(*k, **kw):
+       #print("-> preparing waf")
        mw = 'tmp-waf-'+VERSION
 
        import tarfile, re
 
-       zipType = Params.g_options.zip.strip().lower()
+       zipType = Options.options.zip.strip().lower()
        if zipType not in zip_types:
                zipType = zip_types[0]
 
        #open a file as tar.[extension] for writing
        tar = tarfile.open('%s.tar.%s' % (mw, zipType), "w:%s" % zipType)
-       tarFiles=[]
 
-       lst = os.listdir('wafadmin')
-       files = [os.path.join('wafadmin', s) for s in lst if pyFileExp.match(s) and not s in forbidden]
-       tooldir = os.path.join('wafadmin', 'Tools')
-       lst = os.listdir(tooldir)
-       files += [os.path.join(tooldir, s) for s in lst if pyFileExp.match(s) and not s in forbidden]
+       files = []
+       add3rdparty = []
+       for x in Options.options.add3rdparty.split(','):
+               if os.path.isabs(x):
+                       files.append(x)
+               else:
+                       add3rdparty.append(x + '.py')
+
+       for d in '. Tools extras'.split():
+               dd = os.path.join('waflib', d)
+               for k in os.listdir(dd):
+                       if k == '__init__.py':
+                               files.append(os.path.join(dd, k))
+                               continue
+                       if d == 'extras':
+                               if not k in add3rdparty:
+                                       continue
+                       if k.endswith('.py'):
+                               files.append(os.path.join(dd, k))
+
        for x in files:
                tarinfo = tar.gettarinfo(x, x)
-               tarinfo.uid=tarinfo.gid=1000
-               tarinfo.uname=tarinfo.gname="bozo"
+               tarinfo.uid   = tarinfo.gid   = 0
+               tarinfo.uname = tarinfo.gname = 'root'
                (code, size, cnt) = sfilter(x)
                tarinfo.size = size
+
+               if os.path.isabs(x):
+                       tarinfo.name = 'waflib/extras/' + os.path.split(x)[1]
+
                tar.addfile(tarinfo, code)
        tar.close()
 
-       f = open('waf-light', 'rb')
+       f = open('waf-light', 'rU')
        code1 = f.read()
        f.close()
 
        # now store the revision unique number in waf
-       compute_revision()
-       reg = re.compile('^REVISION=(.*)', re.M)
-       code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
-
-       prefix = Params.g_options.prefix
-       # if the prefix is the default, let's be nice and be platform-independent
-       # just in case the created waf is used on either windows or unix
-       if prefix == Options.default_prefix:
-               prefix = "sys.platform=='win32' and 'c:/temp' or '/usr/local'"
-       else:
-               prefix = '"%s"' % prefix #encase in quotes
+       #compute_revision()
+       #reg = re.compile('^REVISION=(.*)', re.M)
+       #code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
+       code1 = code1.replace("if sys.hexversion<0x206000f:\n\traise ImportError('Python >= 2.6 is required to create the waf file')\n", '')
+       code1 = code1.replace('\timport waflib.extras.compat15#PRELUDE', Options.options.prelude)
 
+       prefix = ''
        reg = re.compile('^INSTALL=(.*)', re.M)
-       code1 = reg.sub(r'INSTALL=%s' % prefix, code1)
+       code1 = reg.sub(r'INSTALL=%r' % prefix, code1)
        #change the tarfile extension in the waf script
        reg = re.compile('bz2', re.M)
        code1 = reg.sub(zipType, code1)
+       if zipType == 'gz':
+               code1 = code1.replace('bunzip2', 'gzip -d')
 
        f = open('%s.tar.%s' % (mw, zipType), 'rb')
        cnt = f.read()
        f.close()
-       code2 = encodeAscii85(cnt)
+
+       # the REVISION value is the md5 sum of the binary blob (facilitate audits)
+       m = md5()
+       m.update(cnt)
+       REVISION = m.hexdigest()
+       reg = re.compile('^REVISION=(.*)', re.M)
+       code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
+
+       def find_unused(kd, ch):
+               for i in range(35, 125):
+                       for j in range(35, 125):
+                               if i==j: continue
+                               if i == 39 or j == 39: continue
+                               if i == 92 or j == 92: continue
+                               s = chr(i) + chr(j)
+                               if -1 == kd.find(s.encode()):
+                                       return (kd.replace(ch.encode(), s.encode()), s)
+               raise
+
+       # The reverse order prevent collisions
+       (cnt, C2) = find_unused(cnt, '\r')
+       (cnt, C1) = find_unused(cnt, '\n')
        f = open('waf', 'wb')
-       f.write(code1)
-       f.write('#==>\n')
-       f.write('#')
-       f.write(code2)
-       f.write('\n')
-       f.write('#<==\n')
+
+       ccc = code1.replace("C1='x'", "C1='%s'" % C1).replace("C2='x'", "C2='%s'" % C2)
+
+       f.write(ccc.encode())
+       f.write(b'#==>\n')
+       f.write(b'#')
+       f.write(cnt)
+       f.write(b'\n')
+       f.write(b'#<==\n')
        f.close()
 
-       if sys.platform == 'win32' or Params.g_options.make_batch:
-               f = open('waf.bat', 'wb')
-               f.write('@python -x waf %* & exit /b\n')
+       if sys.platform == 'win32' or Options.options.make_batch:
+               f = open('waf.bat', 'w')
+               f.write('@python -x "%~dp0waf" %* & exit /b\n')
                f.close()
 
        if sys.platform != 'win32':
-               os.chmod('waf', 0755)
+               os.chmod('waf', Utils.O755)
        os.unlink('%s.tar.%s' % (mw, zipType))
 
 def make_copy(inf, outf):
@@ -288,61 +345,13 @@ def make_copy(inf, outf):
        f.close()
 
 def configure(conf):
-       conf.check_tool('python')
+       conf.load('python')
        conf.check_python_version((2,4))
 
 
 def build(bld):
-       import shutil, re
-
-       if Params.g_commands['install']:
-               if sys.platform == 'win32':
-                       print "Installing Waf on Windows is not possible."
-                       sys.exit(0)
-
-       if Params.g_commands['install']:
-               val = Params.g_options.yes or (not sys.stdin.isatty() or raw_input("Installing Waf is discouraged. Proceed? [y/n]"))
-               if val != True and val != "y": sys.exit(1)
-
-               compute_revision()
-
-               create_waf()
-
-       wafadmin = bld.create_obj('py')
-       wafadmin.find_sources_in_dirs('wafadmin', exts=['.py'])
-       wafadmin.inst_var = 'PREFIX'
-       wafadmin.inst_dir = os.path.join('lib', 'waf-%s-%s' % (VERSION, REVISION), 'wafadmin')
-
-       tools = bld.create_obj('py')
-       tools.find_sources_in_dirs('wafadmin/Tools', exts=['.py'])
-       tools.inst_var = 'PREFIX'
-       tools.inst_dir = os.path.join(wafadmin.inst_dir, 'Tools')
-
-       Common.install_files('PREFIX', 'bin', 'waf', chmod=0755)
-
-       #print "waf is now installed in %s [%s, %s]" % (prefix, wafadmindir, binpath)
-       #print "make sure the PATH contains %s/bin:$PATH" % prefix
-
-
-# the init function is called right after the command-line arguments are parsed
-# it is run before configure(), build() and shutdown()
-# in this case it calls sys.exit(0) to terminate the program
-def init():
-       if Params.g_options.setver: # maintainer only (ita)
-               ver = Params.g_options.setver
-               hexver = '0x'+ver.replace('.','0')
-               os.popen("""perl -pi -e 's/^VERSION=(.*)?$/VERSION="%s"/' wscript""" % ver).close()
-               os.popen("""perl -pi -e 's/^VERSION=(.*)?$/VERSION="%s"/' waf-light""" % ver).close()
-               os.popen("""perl -pi -e 's/^g_version(.*)?$/g_version="%s"/' wafadmin/Params.py""" % ver).close()
-               os.popen("""perl -pi -e 's/^HEXVERSION(.*)?$/HEXVERSION = %s/' wafadmin/Constants.py""" % hexver).close()
-               sys.exit(0)
-       elif Params.g_options.waf:
-               create_waf()
-               sys.exit(0)
-       elif Params.g_commands['check']:
-               import Test
-               Test.run_tests()
-               sys.exit(0)
+       waf = bld.path.make_node('waf') # create the node right here
+       bld(name='create_waf', rule=create_waf, target=waf, always=True, color='PINK', update_outputs=True)
 
 #def dist():
 #      import Scripting