3 # Thomas Nagy, 2005-2010
6 to make a custom waf file use the option --tools
8 To add a tool that does not exist in the folder compat15, pass an absolute path:
9 ./waf-light --make-waf --tools=compat15,/comp/waf/aba.py --prelude=$'\tfrom waflib.extras import aba\n\taba.foo()'
20 demos = ['cpp', 'qt4', 'tex', 'ocaml', 'kde3', 'adv', 'cc', 'idl', 'docbook', 'xmlwaf', 'gnome']
21 zip_types = ['bz2', 'gz']
23 PRELUDE = '\timport waflib.extras.compat15'
25 #from tokenize import *
28 import os, sys, re, io, optparse
30 from waflib import Utils, Options
31 from hashlib import md5
33 from waflib import Configure
34 Configure.autoconfig = 1
36 def sub_file(fname, lst):
42 for (key, val) in lst:
43 re_pat = re.compile(key, re.M)
44 txt = re_pat.sub(val, txt)
50 print("------> Executing code from the top-level wscript <-----")
52 if Options.options.setver: # maintainer only (ita)
53 ver = Options.options.setver
54 hexver = Utils.num2ver(ver)
55 hexver = '0x%x'%hexver
56 sub_file('wscript', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))
57 sub_file('waf-light', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))
60 pats.append(('^WAFVERSION=(.*)', 'WAFVERSION="%s"' % ver))
61 pats.append(('^HEXVERSION(.*)', 'HEXVERSION=%s' % hexver))
64 rev = k[0].cmd_and_log('svnversion').strip().replace('M', '')
65 pats.append(('^WAFREVISION(.*)', 'WAFREVISION="%s"' % rev))
69 sub_file('waflib/Context.py', pats)
72 elif Options.options.waf:
78 # some tests clobber g_module. We must preserve it here, otherwise we get an error
79 # about an undefined shutdown function
85 # this function is called before any other for parsing the command-line
89 opt.add_option('--make-waf', action='store_true', default=False,
90 help='creates the waf script', dest='waf')
92 opt.add_option('--zip-type', action='store', default='bz2',
93 help='specify the zip type [Allowed values: %s]' % ' '.join(zip_types), dest='zip')
95 opt.add_option('--make-batch', action='store_true', default=False,
96 help='creates a convenience waf.bat file (done automatically on win32 systems)',
99 opt.add_option('--yes', action='store_true', default=False,
100 help=optparse.SUPPRESS_HELP,
103 # those ones are not too interesting
104 opt.add_option('--set-version', default='',
105 help='sets the version number for waf releases (for the maintainer)', dest='setver')
107 opt.add_option('--strip', action='store_true', default=True,
108 help='shrinks waf (strip docstrings, saves 33kb)',
109 dest='strip_comments')
110 opt.add_option('--nostrip', action='store_false', help='no shrinking',
111 dest='strip_comments')
112 opt.add_option('--tools', action='store', help='Comma-separated 3rd party tools to add, eg: "compat,ocaml" [Default: "compat15"]',
113 dest='add3rdparty', default='compat15')
114 opt.add_option('--prelude', action='store', help='Code to execute before calling waf', dest='prelude', default=PRELUDE)
117 def compute_revision():
120 def visit(arg, dirname, names):
121 for pos, name in enumerate(names):
122 if name[0] == '.' or name in ['_build_', 'build']:
124 elif name.endswith('.py'):
125 arg.append(os.path.join(dirname, name))
127 os.path.walk('waflib', visit, sources)
130 for source in sources:
131 f = file(source,'rb')
134 readString = f.read(readBytes)
136 readBytes = len(readString)
138 REVISION = m.hexdigest()
140 def process_tokens(tokens):
142 prev = tokenize.NEWLINE
147 for (type, token, start, end, line) in tokens:
148 token = token.replace('\r\n', '\n')
149 if type == tokenize.NEWLINE:
151 accu.append(indent * '\t')
152 ln = "".join(line_buf)
153 if ln == 'if __name__=="__main__":': break
154 #ln = ln.replace('\n', '')
158 prev = tokenize.NEWLINE
159 elif type == tokenize.INDENT:
161 elif type == tokenize.DEDENT:
163 elif type == tokenize.NAME:
164 if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
165 line_buf.append(token)
166 elif type == tokenize.NUMBER:
167 if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
168 line_buf.append(token)
169 elif type == tokenize.STRING:
170 if not line_buf and token.startswith('"'): pass
171 else: line_buf.append(token)
172 elif type == tokenize.COMMENT:
174 elif type == tokenize.OP:
175 line_buf.append(token)
177 if token != "\n": line_buf.append(token)
185 deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
186 def process_decorators(body):
187 lst = body.split('\n')
190 buf = [] # put the decorator lines
192 if line.startswith('@'):
195 name = deco_re.sub('\\2', line)
197 raise IOError("decorator not followed by a function!" + line)
199 all_deco.append("%s(%s)" % (x, name))
204 return "\n".join(accu+all_deco)
207 if sys.version_info[0] >= 3 and Options.options.strip_comments:
209 tk = tokenize.tokenize(f.readline)
210 next(tk) # the first one is always tokenize.ENCODING for Python 3, ignore it
211 cnt = process_tokens(tk)
212 elif Options.options.strip_comments and path.endswith('.py'):
214 cnt = process_tokens(tokenize.generate_tokens(f.readline))
220 if path.endswith('.py') :
221 cnt = process_decorators(cnt)
223 if cnt.find('set(') > -1:
224 cnt = 'import sys\nif sys.hexversion < 0x020400f0: from sets import Set as set\n' + cnt
225 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
227 return (io.BytesIO(cnt.encode('utf-8')), len(cnt), cnt)
229 def create_waf(*k, **kw):
230 #print("-> preparing waf")
231 mw = 'tmp-waf-'+VERSION
235 zipType = Options.options.zip.strip().lower()
236 if zipType not in zip_types:
237 zipType = zip_types[0]
239 #open a file as tar.[extension] for writing
240 tar = tarfile.open('%s.tar.%s' % (mw, zipType), "w:%s" % zipType)
244 for x in Options.options.add3rdparty.split(','):
248 add3rdparty.append(x + '.py')
250 for d in '. Tools extras'.split():
251 dd = os.path.join('waflib', d)
252 for k in os.listdir(dd):
253 if k == '__init__.py':
254 files.append(os.path.join(dd, k))
257 if not k in add3rdparty:
259 if k.endswith('.py'):
260 files.append(os.path.join(dd, k))
263 tarinfo = tar.gettarinfo(x, x)
264 tarinfo.uid = tarinfo.gid = 0
265 tarinfo.uname = tarinfo.gname = 'root'
266 (code, size, cnt) = sfilter(x)
270 tarinfo.name = 'waflib/extras/' + os.path.split(x)[1]
272 tar.addfile(tarinfo, code)
275 f = open('waf-light', 'rU')
279 # now store the revision unique number in waf
281 #reg = re.compile('^REVISION=(.*)', re.M)
282 #code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
283 code1 = code1.replace("if sys.hexversion<0x206000f:\n\traise ImportError('Python >= 2.6 is required to create the waf file')\n", '')
284 code1 = code1.replace('\timport waflib.extras.compat15#PRELUDE', Options.options.prelude)
287 reg = re.compile('^INSTALL=(.*)', re.M)
288 code1 = reg.sub(r'INSTALL=%r' % prefix, code1)
289 #change the tarfile extension in the waf script
290 reg = re.compile('bz2', re.M)
291 code1 = reg.sub(zipType, code1)
293 code1 = code1.replace('bunzip2', 'gzip -d')
295 f = open('%s.tar.%s' % (mw, zipType), 'rb')
299 # the REVISION value is the md5 sum of the binary blob (facilitate audits)
302 REVISION = m.hexdigest()
303 reg = re.compile('^REVISION=(.*)', re.M)
304 code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
306 def find_unused(kd, ch):
307 for i in range(35, 125):
308 for j in range(35, 125):
310 if i == 39 or j == 39: continue
311 if i == 92 or j == 92: continue
313 if -1 == kd.find(s.encode()):
314 return (kd.replace(ch.encode(), s.encode()), s)
317 # The reverse order prevent collisions
318 (cnt, C2) = find_unused(cnt, '\r')
319 (cnt, C1) = find_unused(cnt, '\n')
320 f = open('waf', 'wb')
322 ccc = code1.replace("C1='x'", "C1='%s'" % C1).replace("C2='x'", "C2='%s'" % C2)
324 f.write(ccc.encode())
332 if sys.platform == 'win32' or Options.options.make_batch:
333 f = open('waf.bat', 'w')
334 f.write('@python -x "%~dp0waf" %* & exit /b\n')
337 if sys.platform != 'win32':
338 os.chmod('waf', Utils.O755)
339 os.unlink('%s.tar.%s' % (mw, zipType))
341 def make_copy(inf, outf):
342 (a, b, cnt) = sfilter(inf)
349 conf.check_python_version((2,4))
353 waf = bld.path.make_node('waf') # create the node right here
354 bld(name='create_waf', rule=create_waf, target=waf, always=True, color='PINK', update_outputs=True)
358 # Scripting.g_dist_exts += ['Weak.py'] # shows how to exclude a file from dist
359 # Scripting.Dist(APPNAME, VERSION)