docs
[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.2"
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, base64, shutil, re, random, io, optparse, tempfile
29
30 from waflib import Utils, Options, Build
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         accu_deco = []
145         indent = 0
146         line_buf = []
147
148         for (type, token, start, end, line) in tokens:
149                 token = token.replace('\r\n', '\n')
150                 if type == tokenize.NEWLINE:
151                         if line_buf:
152                                 accu.append(indent * '\t')
153                                 ln = "".join(line_buf)
154                                 if ln == 'if __name__=="__main__":': break
155                                 #ln = ln.replace('\n', '')
156                                 accu.append(ln)
157                                 accu.append('\n')
158                                 line_buf = []
159                                 prev = tokenize.NEWLINE
160                 elif type == tokenize.INDENT:
161                         indent += 1
162                 elif type == tokenize.DEDENT:
163                         indent -= 1
164                 elif type == tokenize.NAME:
165                         if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
166                         line_buf.append(token)
167                 elif type == tokenize.NUMBER:
168                         if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
169                         line_buf.append(token)
170                 elif type == tokenize.STRING:
171                         if not line_buf and token.startswith('"'): pass
172                         else: line_buf.append(token)
173                 elif type == tokenize.COMMENT:
174                         pass
175                 elif type == tokenize.OP:
176                         line_buf.append(token)
177                 else:
178                         if token != "\n": line_buf.append(token)
179
180                 if token != '\n':
181                         prev = type
182
183         body = "".join(accu)
184         return body
185
186 deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
187 def process_decorators(body):
188         lst = body.split('\n')
189         accu = []
190         all_deco = []
191         buf = [] # put the decorator lines
192         for line in lst:
193                 if line.startswith('@'):
194                         buf.append(line[1:])
195                 elif buf:
196                         name = deco_re.sub('\\2', line)
197                         if not name:
198                                 raise IOError("decorator not followed by a function!" + line)
199                         for x in buf:
200                                 all_deco.append("%s(%s)" % (x, name))
201                         accu.append(line)
202                         buf = []
203                 else:
204                         accu.append(line)
205         return "\n".join(accu+all_deco)
206
207 def sfilter(path):
208         if sys.version_info[0] >= 3 and Options.options.strip_comments:
209                 f = open(path, "rb")
210                 tk = tokenize.tokenize(f.readline)
211                 next(tk) # the first one is always tokenize.ENCODING for Python 3, ignore it
212                 cnt = process_tokens(tk)
213         elif Options.options.strip_comments:
214                 f = open(path, "r")
215                 cnt = process_tokens(tokenize.generate_tokens(f.readline))
216         else:
217                 f = open(path, "r")
218                 cnt = f.read()
219
220         f.close()
221         if path.endswith('.py') :
222                 cnt = process_decorators(cnt)
223
224                 if cnt.find('set(') > -1:
225                         cnt = 'import sys\nif sys.hexversion < 0x020400f0: from sets import Set as set\n' + cnt
226                 cnt = '#! /usr/bin/env python\n# encoding: utf-8\n# WARNING! All changes made to this file will be lost!\n\n' + cnt
227
228         return (io.BytesIO(cnt.encode('utf-8')), len(cnt), cnt)
229
230 def create_waf(*k, **kw):
231         #print("-> preparing waf")
232         mw = 'tmp-waf-'+VERSION
233
234         import tarfile, re
235
236         zipType = Options.options.zip.strip().lower()
237         if zipType not in zip_types:
238                 zipType = zip_types[0]
239
240         #open a file as tar.[extension] for writing
241         tar = tarfile.open('%s.tar.%s' % (mw, zipType), "w:%s" % zipType)
242         tarFiles = []
243
244         files = []
245         add3rdparty = []
246         for x in Options.options.add3rdparty.split(','):
247                 if os.path.isabs(x):
248                         files.append(x)
249                 else:
250                         add3rdparty.append(x + '.py')
251
252         for d in '. Tools extras'.split():
253                 dd = os.path.join('waflib', d)
254                 for k in os.listdir(dd):
255                         if k == '__init__.py':
256                                 files.append(os.path.join(dd, k))
257                                 continue
258                         if d == 'extras':
259                                 if not k in add3rdparty:
260                                         continue
261                         if k.endswith('.py'):
262                                 files.append(os.path.join(dd, k))
263
264         for x in files:
265                 tarinfo = tar.gettarinfo(x, x)
266                 tarinfo.uid   = tarinfo.gid   = 0
267                 tarinfo.uname = tarinfo.gname = 'root'
268                 (code, size, cnt) = sfilter(x)
269                 tarinfo.size = size
270
271                 if os.path.isabs(x):
272                         tarinfo.name = 'waflib/extras/' + os.path.split(x)[1]
273
274                 tar.addfile(tarinfo, code)
275         tar.close()
276
277         f = open('waf-light', 'rU')
278         code1 = f.read()
279         f.close()
280
281         # now store the revision unique number in waf
282         #compute_revision()
283         #reg = re.compile('^REVISION=(.*)', re.M)
284         #code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
285         code1 = code1.replace("if sys.hexversion<0x206000f:\n\traise ImportError('Python >= 2.6 is required to create the waf file')\n", '')
286         code1 = code1.replace('\timport waflib.extras.compat15#PRELUDE', Options.options.prelude)
287
288         prefix = ''
289         reg = re.compile('^INSTALL=(.*)', re.M)
290         code1 = reg.sub(r'INSTALL=%r' % prefix, code1)
291         #change the tarfile extension in the waf script
292         reg = re.compile('bz2', re.M)
293         code1 = reg.sub(zipType, code1)
294         if zipType == 'gz':
295                 code1 = code1.replace('bunzip2', 'gzip -d')
296
297         f = open('%s.tar.%s' % (mw, zipType), 'rb')
298         cnt = f.read()
299         f.close()
300
301         # the REVISION value is the md5 sum of the binary blob (facilitate audits)
302         m = md5()
303         m.update(cnt)
304         REVISION = m.hexdigest()
305         reg = re.compile('^REVISION=(.*)', re.M)
306         code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)
307
308         def find_unused(kd, ch):
309                 for i in range(35, 125):
310                         for j in range(35, 125):
311                                 if i==j: continue
312                                 if i == 39 or j == 39: continue
313                                 if i == 92 or j == 92: continue
314                                 s = chr(i) + chr(j)
315                                 if -1 == kd.find(s.encode()):
316                                         return (kd.replace(ch.encode(), s.encode()), s)
317                 raise
318
319         # The reverse order prevent collisions
320         (cnt, C2) = find_unused(cnt, '\r')
321         (cnt, C1) = find_unused(cnt, '\n')
322         f = open('waf', 'wb')
323
324         ccc = code1.replace("C1='x'", "C1='%s'" % C1).replace("C2='x'", "C2='%s'" % C2)
325
326         f.write(ccc.encode())
327         f.write(b'#==>\n')
328         f.write(b'#')
329         f.write(cnt)
330         f.write(b'\n')
331         f.write(b'#<==\n')
332         f.close()
333
334         if sys.platform == 'win32' or Options.options.make_batch:
335                 f = open('waf.bat', 'w')
336                 f.write('@python -x "%~dp0waf" %* & exit /b\n')
337                 f.close()
338
339         if sys.platform != 'win32':
340                 os.chmod('waf', Utils.O755)
341         os.unlink('%s.tar.%s' % (mw, zipType))
342
343 def make_copy(inf, outf):
344         (a, b, cnt) = sfilter(inf)
345         f = open(outf, "wb")
346         f.write(cnt)
347         f.close()
348
349 def configure(conf):
350         conf.load('python')
351         conf.check_python_version((2,4))
352
353
354 def build(bld):
355         waf = bld.path.make_node('waf') # create the node right here
356         bld(name='create_waf', rule=create_waf, target=waf, always=True, color='PINK', update_outputs=True)
357
358 #def dist():
359 #       import Scripting
360 #       Scripting.g_dist_exts += ['Weak.py'] # shows how to exclude a file from dist
361 #       Scripting.Dist(APPNAME, VERSION)
362