#1034
[tridge/waf-svn.git/.git] / wscript
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2010
4
5 """
6 to make a custom waf file use the option --tools
7
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()'
10 """
11
12
13 VERSION="1.6.8"
14 APPNAME='waf'
15 REVISION=''
16
17 top = '.'
18 out = 'build'
19
20 demos = ['cpp', 'qt4', 'tex', 'ocaml', 'kde3', 'adv', 'cc', 'idl', 'docbook', 'xmlwaf', 'gnome']
21 zip_types = ['bz2', 'gz']
22
23 PRELUDE = '\timport waflib.extras.compat15'
24
25 #from tokenize import *
26 import tokenize
27
28 import os, sys, re, io, optparse
29
30 from waflib import Utils, Options
31 from hashlib import md5
32
33 from waflib import Configure
34 Configure.autoconfig = 1
35
36 def sub_file(fname, lst):
37
38         f = open(fname, 'rU')
39         txt = f.read()
40         f.close()
41
42         for (key, val) in lst:
43                 re_pat = re.compile(key, re.M)
44                 txt = re_pat.sub(val, txt)
45
46         f = open(fname, 'w')
47         f.write(txt)
48         f.close()
49
50 print("------> Executing code from the top-level wscript <-----")
51 def init(*k, **kw):
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), ))
58
59                 pats = []
60                 pats.append(('^WAFVERSION=(.*)', 'WAFVERSION="%s"' % ver))
61                 pats.append(('^HEXVERSION(.*)', 'HEXVERSION=%s' % hexver))
62
63                 try:
64                         rev = k[0].cmd_and_log('svnversion').strip().replace('M', '')
65                         pats.append(('^WAFREVISION(.*)', 'WAFREVISION="%s"' % rev))
66                 except:
67                         pass
68
69                 sub_file('waflib/Context.py', pats)
70
71                 sys.exit(0)
72         elif Options.options.waf:
73                 create_waf()
74                 sys.exit(0)
75
76 def check(ctx):
77         sys.path.insert(0,'')
78         # some tests clobber g_module. We must preserve it here, otherwise we get an error
79         # about an undefined shutdown function
80         mod = Utils.g_module
81         import test.Test
82         test.Test.run_tests()
83         Utils.g_module = mod
84
85 # this function is called before any other for parsing the command-line
86 def options(opt):
87
88         # generate waf
89         opt.add_option('--make-waf', action='store_true', default=False,
90                 help='creates the waf script', dest='waf')
91
92         opt.add_option('--zip-type', action='store', default='bz2',
93                 help='specify the zip type [Allowed values: %s]' % ' '.join(zip_types), dest='zip')
94
95         opt.add_option('--make-batch', action='store_true', default=False,
96                 help='creates a convenience waf.bat file (done automatically on win32 systems)',
97                 dest='make_batch')
98
99         opt.add_option('--yes', action='store_true', default=False,
100                 help=optparse.SUPPRESS_HELP,
101                 dest='yes')
102
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')
106
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)
115         opt.load('python')
116
117 def compute_revision():
118         global REVISION
119
120         def visit(arg, dirname, names):
121                 for pos, name in enumerate(names):
122                         if name[0] == '.' or name in ['_build_', 'build']:
123                                 del names[pos]
124                         elif name.endswith('.py'):
125                                 arg.append(os.path.join(dirname, name))
126         sources = []
127         os.path.walk('waflib', visit, sources)
128         sources.sort()
129         m = md5()
130         for source in sources:
131                 f = file(source,'rb')
132                 readBytes = 100000
133                 while (readBytes):
134                         readString = f.read(readBytes)
135                         m.update(readString)
136                         readBytes = len(readString)
137                 f.close()
138         REVISION = m.hexdigest()
139
140 def process_tokens(tokens):
141         accu = []
142         prev = tokenize.NEWLINE
143
144         indent = 0
145         line_buf = []
146
147         for (type, token, start, end, line) in tokens:
148                 token = token.replace('\r\n', '\n')
149                 if type == tokenize.NEWLINE:
150                         if line_buf:
151                                 accu.append(indent * '\t')
152                                 ln = "".join(line_buf)
153                                 if ln == 'if __name__=="__main__":': break
154                                 #ln = ln.replace('\n', '')
155                                 accu.append(ln)
156                                 accu.append('\n')
157                                 line_buf = []
158                                 prev = tokenize.NEWLINE
159                 elif type == tokenize.INDENT:
160                         indent += 1
161                 elif type == tokenize.DEDENT:
162                         indent -= 1
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:
173                         pass
174                 elif type == tokenize.OP:
175                         line_buf.append(token)
176                 else:
177                         if token != "\n": line_buf.append(token)
178
179                 if token != '\n':
180                         prev = type
181
182         body = "".join(accu)
183         return body
184
185 deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
186 def process_decorators(body):
187         lst = body.split('\n')
188         accu = []
189         all_deco = []
190         buf = [] # put the decorator lines
191         for line in lst:
192                 if line.startswith('@'):
193                         buf.append(line[1:])
194                 elif buf:
195                         name = deco_re.sub('\\2', line)
196                         if not name:
197                                 raise IOError("decorator not followed by a function!" + line)
198                         for x in buf:
199                                 all_deco.append("%s(%s)" % (x, name))
200                         accu.append(line)
201                         buf = []
202                 else:
203                         accu.append(line)
204         return "\n".join(accu+all_deco)
205
206 def sfilter(path):
207         if sys.version_info[0] >= 3 and Options.options.strip_comments:
208                 f = open(path, "rb")
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'):
213                 f = open(path, "r")
214                 cnt = process_tokens(tokenize.generate_tokens(f.readline))
215         else:
216                 f = open(path, "r")
217                 cnt = f.read()
218
219         f.close()
220         if path.endswith('.py') :
221                 cnt = process_decorators(cnt)
222
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
226
227         return (io.BytesIO(cnt.encode('utf-8')), len(cnt), cnt)
228
229 def create_waf(*k, **kw):
230         #print("-> preparing waf")
231         mw = 'tmp-waf-'+VERSION
232
233         import tarfile, re
234
235         zipType = Options.options.zip.strip().lower()
236         if zipType not in zip_types:
237                 zipType = zip_types[0]
238
239         #open a file as tar.[extension] for writing
240         tar = tarfile.open('%s.tar.%s' % (mw, zipType), "w:%s" % zipType)
241
242         files = []
243         add3rdparty = []
244         for x in Options.options.add3rdparty.split(','):
245                 if os.path.isabs(x):
246                         files.append(x)
247                 else:
248                         add3rdparty.append(x + '.py')
249
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))
255                                 continue
256                         if d == 'extras':
257                                 if not k in add3rdparty:
258                                         continue
259                         if k.endswith('.py'):
260                                 files.append(os.path.join(dd, k))
261
262         for x in files:
263                 tarinfo = tar.gettarinfo(x, x)
264                 tarinfo.uid   = tarinfo.gid   = 0
265                 tarinfo.uname = tarinfo.gname = 'root'
266                 (code, size, cnt) = sfilter(x)
267                 tarinfo.size = size
268
269                 if os.path.isabs(x):
270                         tarinfo.name = 'waflib/extras/' + os.path.split(x)[1]
271
272                 tar.addfile(tarinfo, code)
273         tar.close()
274
275         f = open('waf-light', 'rU')
276         code1 = f.read()
277         f.close()
278
279         # now store the revision unique number in waf
280         #compute_revision()
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)
285
286         prefix = ''
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)
292         if zipType == 'gz':
293                 code1 = code1.replace('bunzip2', 'gzip -d')
294
295         f = open('%s.tar.%s' % (mw, zipType), 'rb')
296         cnt = f.read()
297         f.close()
298
299         # the REVISION value is the md5 sum of the binary blob (facilitate audits)
300         m = md5()
301         m.update(cnt)
302         REVISION = m.hexdigest()
303         reg = re.compile('^REVISION=(.*)', re.M)
304         code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
305
306         def find_unused(kd, ch):
307                 for i in range(35, 125):
308                         for j in range(35, 125):
309                                 if i==j: continue
310                                 if i == 39 or j == 39: continue
311                                 if i == 92 or j == 92: continue
312                                 s = chr(i) + chr(j)
313                                 if -1 == kd.find(s.encode()):
314                                         return (kd.replace(ch.encode(), s.encode()), s)
315                 raise
316
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')
321
322         ccc = code1.replace("C1='x'", "C1='%s'" % C1).replace("C2='x'", "C2='%s'" % C2)
323
324         f.write(ccc.encode())
325         f.write(b'#==>\n')
326         f.write(b'#')
327         f.write(cnt)
328         f.write(b'\n')
329         f.write(b'#<==\n')
330         f.close()
331
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')
335                 f.close()
336
337         if sys.platform != 'win32':
338                 os.chmod('waf', Utils.O755)
339         os.unlink('%s.tar.%s' % (mw, zipType))
340
341 def make_copy(inf, outf):
342         (a, b, cnt) = sfilter(inf)
343         f = open(outf, "wb")
344         f.write(cnt)
345         f.close()
346
347 def configure(conf):
348         conf.load('python')
349         conf.check_python_version((2,4))
350
351
352 def build(bld):
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)
355
356 #def dist():
357 #       import Scripting
358 #       Scripting.g_dist_exts += ['Weak.py'] # shows how to exclude a file from dist
359 #       Scripting.Dist(APPNAME, VERSION)
360