1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
|
#! /usr/bin/env python
##########################################################################
## ##
## Build script of the Nimrod Compiler ##
## (c) 2008 Andreas Rumpf ##
## ##
##########################################################################
import os, os.path, sys, re, shutil, cPickle, time, getopt, glob, zlib
from string import split, replace, lower, join, find, strip
if sys.version[0] >= "3": # this script does not work with Python 3.0
sys.exit("wrong python version: use Python 1.5.2 - 2.6")
True = 0 == 0 # Python 1.5 does not have True and False :-(
False = 0 == 1
# --------------------- constants ----------------------------------------
NIMROD_VERSION = '0.7.4'
# This string contains Nimrod's version. It is the only place
# where the version needs to be updated. The rest is done by
# the build process automatically. It is replaced **everywhere**
# automatically!
# Format is: Major.Minor.Patch
# Major part: plan is to use number 1 for the first version that is stable;
# higher versions may be incompatible with previous versions
# Minor part: incremented if new features are added (but is completely
# backwards-compatible)
# Patch level: is increased for every patch
EXPLAIN = True
force = False
GENERATE_DIFF = False
# if set, a diff.log file is generated when bootstrapping
USE_FPC = True
BOOTCMD = "%s cc --compile:build/platdef.c %s rod/nimrod.nim"
# the command used for bootstrapping
# --------------------------------------------------------------------------
def Error(msg): sys.exit("[Koch] *** ERROR: " + msg)
def Warn(msg): print "[Koch] *** WARNING: " + msg
def Echo(msg): print "[Koch] " + msg
def _Info(msg): print "[Koch] " + msg
_FINGERPRINTS_FILE = "koch.dat"
# in this file all the fingerprints are kept to allow recognizing when a file
# has changed. This works reliably, which cannot be said from just taking
# filetime-stamps.
def FileCmp(filenameA, filenameB):
SIZE = 4096*2
result = True
a = open(filenameA, "rb")
b = open(filenameB, "rb")
while True:
x = a.read(SIZE)
y = b.read(SIZE)
if x != y:
result = False
break
elif len(x) < SIZE: # EOF?
break
a.close()
b.close()
return result
def Subs(frmt, **substitution):
import string
chars = string.digits+string.letters+"_"
d = substitution
result = []
i = 0
while i < len(frmt):
if frmt[i] == '$':
i = i+1
if frmt[i] == '$':
result.append('$')
i = i+1
elif frmt[i] == '{':
i = i+1
j = i
while frmt[i] != '}': i = i+1
i = i+1 # skip }
result.append(d[frmt[j:i-1]])
elif frmt[i] in string.letters+"_":
j = i
i = i+1
while i < len(frmt) and frmt[i] in chars: i = i + 1
result.append(d[frmt[j:i]])
else:
assert(false)
else:
result.append(frmt[i])
i = i+1
return join(result, "")
def SplitArg(s):
if ':' in s: c = ':'
elif '=' in s: c = '='
else: return (s, '')
i = s.find(c)
return (s[:i], s[i+1:])
_baseDir = os.getcwd()
BaseDir = _baseDir
def Path(a):
# Gets a UNIX like path and converts it to a path on this platform.
# With UNIX like, I mean: slashes, not backslashes, only relative
# paths ('../etc') can be used
result = a
if os.sep != "/": result = replace(result, "/", os.sep)
if os.pardir != "..": result = replace(result, "..", os.pardir)
return result
def Join(*args):
result = []
for a in args[:-1]:
result.append(a)
if result[-1] != "/": result.append("/")
result.append(args[-1])
return replace(join(result, ""), "//", "/")
def Exec(command):
c = Path(command)
Echo(c)
result = os.system(c)
if result != 0: Error("execution of an external program failed")
return result
def TryExec(command):
c = Path(command)
Echo(c)
result = os.system(c)
return result
def RawExec(command):
Echo(command)
result = os.system(command)
if result != 0: Error("execution of an external program failed")
return result
def Remove(f):
try:
os.remove(Path(f))
except OSError:
Warn("could not remove: %s" % f)
def Move(src, dest):
try:
m = shutil.move
except AttributeError:
def f(src, dest):
shutil.copy(src, dest)
Remove(src)
m = f
s = Path(src)
d = Path(dest)
try:
m(s, d)
except IOError, OSError:
Warn("could not move %s to %s" % (s, d))
def Copy(src, dest):
s = Path(src)
d = Path(dest)
try:
shutil.copyfile(s, d)
except IOError, OSError:
Warn("could not copy %s to %s" % (s, d))
def RemoveDir(f):
try:
shutil.rmtree(Path(f))
except OSError:
Warn("could not remove: %s" % f)
def Exists(f): return os.path.exists(Path(f))
def Chdir(dest):
d = Path(dest)
try:
os.chdir(d)
except OSError:
Warn("could not switch to directory: " + d)
def Mkdir(dest):
d = Path(dest)
try:
os.mkdir(d)
except OSError:
Warn("could not create directory: " + d)
def Glob(pattern): # needed because glob.glob() is buggy on Windows 95:
# things like tests/t*.mor won't work
global _baseDir
(head, tail) = os.path.split(Path(pattern))
result = []
try:
os.chdir(os.path.join(_baseDir, head))
try:
for f in glob.glob(tail): result.append(os.path.join(head, f))
except OSError:
result = []
finally:
os.chdir(_baseDir)
return result
def FilenameNoExt(f):
return os.path.splitext(os.path.basename(f))[0]
def _Ext(trunc, posixFormat, winFormat):
(head, tail) = os.path.split(Path(trunc))
if os.name == "posix": frmt = posixFormat
else: frmt = winFormat
return os.path.join(head, Subs(frmt, trunc=tail))
def DynExt(trunc):
return _Ext(trunc, 'lib${trunc}.so', '${trunc}.dll')
def LibExt(trunc):
return _Ext(trunc, '${trunc}.a', '${trunc}.lib')
def ScriptExt(trunc):
return _Ext(trunc, '${trunc}.sh', '${trunc}.bat')
def ExeExt(trunc):
return _Ext(trunc, '${trunc}', '${trunc}.exe')
def MakeExecutable(file):
os.chmod(file, 493)
class Changed:
""" Returns a Changed object. This object evals to true if one of the
given files has changed, false otherwise in a boolean context. You have
to call the object's success() method if the building has been a success.
Example:
c = Changed("unique_name", "file1.pas file2.pas file3.pas")
if c.check():
Exec("fpc file1.pas")
# Exec raises an exception if it fails, thus if we get to here, it was
# a success:
c.success()
"""
def __init__(self, id, files, explain=False,
fingerprintsfile=_FINGERPRINTS_FILE):
# load the fingerprints file:
# fingerprints is a dict[target, files] where files is a dict[filename, hash]
self.fingers = {} # default value
if Exists(fingerprintsfile):
try:
self.fingers = cPickle.load(open(fingerprintsfile))
except OSError:
Error("Cannot read from " + fingerprintsfile)
self.filename = fingerprintsfile
self.id = id
self.files = files
self._hashStr = zlib.adler32 # our hash function
self.explain = explain
def _hashFile(self, f):
x = open(f)
result = self._hashStr(x.read())
x.close() # for other Python implementations
return result
def check(self):
if type(self.files) == type(""):
self.files = split(self.files)
result = False
target = self.id
if not self.fingers.has_key(target):
self.fingers[target] = {}
if self.explain: _Info("no entries for target '%s'" % target)
result = True
for d in self.files:
if Exists(d):
n = self._hashFile(d)
if not self.fingers[target].has_key(d) or n != self.fingers[target][d]:
result = True
if self.explain: _Info("'%s' modified since last build" % d)
self.fingers[target][d] = n
else:
Warn("'%s' does not exist!" % d)
result = True
return result
def update(self, filename):
self.fingers[self.id][filename] = self._hashFile(filename)
def success(self):
cPickle.dump(self.fingers, open(self.filename, "w+"))
# --------------------------------------------------------------------------
def CogRule(name, filename, dependson):
def processCog(filename):
from cogapp import Cog
ret = Cog().main([sys.argv[0], "-r", Path(filename)])
return ret
c = Changed(name, filename + " " + dependson, EXPLAIN)
if c.check() or force:
if processCog(filename) == 0:
c.update(filename)
c.success()
else:
Error("Cog failed")
_nim_exe = os.path.join(os.getcwd(), "bin", ExeExt("nim"))
_output_obj = os.path.join(os.getcwd(), "obj")
FPC_CMD = (r"fpc -Cs16777216 -gl -bl -Crtoi -Sgidh -vw -Se1 -o%s "
r"-FU%s %s") % (_nim_exe, _output_obj,
os.path.join(os.getcwd(), "nim", "nimrod.pas"))
def buildRod(options):
Exec("nim compile --compile:build/platdef.c %s rod/nimrod" % options)
Move(ExeExt("rod/nimrod"), ExeExt("bin/nimrod"))
def cmd_nim():
CogRule("nversion", "nim/nversion.pas", "koch.py")
CogRule("msgs", "nim/msgs.pas", "data/messages.yml")
CogRule("ast", "nim/ast.pas", "koch.py data/magic.yml data/ast.yml")
CogRule("scanner", "nim/scanner.pas", "data/keywords.txt")
CogRule("paslex", "nim/paslex.pas", "data/pas_keyw.yml")
CogRule("wordrecg", "nim/wordrecg.pas", "data/keywords.txt")
CogRule("commands", "nim/commands.pas",
"data/basicopt.txt data/advopt.txt")
CogRule("macros", "lib/macros.nim", "koch.py data/ast.yml")
c = Changed("nim", Glob("nim/*.pas"), EXPLAIN)
if c.check() or force:
Exec(FPC_CMD)
if Exists(ExeExt("bin/nim")):
c.success()
return True
return False
def cmd_rod(options):
prereqs = Glob("lib/*.nim") + Glob("rod/*.nim") + [
"lib/nimbase.h", "lib/dlmalloc.c", "lib/dlmalloc.h",
"config/nimrod.cfg"]
c = Changed("rod", prereqs, EXPLAIN)
if c.check() or cmd_nim() or force:
buildRod(options)
if Exists(ExeExt("bin/nimrod")):
c.success()
# ------------------- constants -----------------------------------------------
HELP = """\
+-----------------------------------------------------------------+
| Maintenance script for Nimrod |
| Version %s|
| (c) 2008 Andreas Rumpf |
+-----------------------------------------------------------------+
Your Python version: %s
Usage:
koch.py [options] command [options for command]
Options:
--force, -f, -B, -b forces rebuild
--diff generates a diff.log file when bootstrapping
--help, -h shows this help and quits
--no_fpc bootstrap without FPC
Possible Commands:
nim builds the Pascal version of Nimrod
rod [options] builds the Nimrod version of Nimrod (with options)
clean cleans Nimrod project; removes generated files
boot [options] bootstraps with given command line options
rodsrc generates Nimrod version from Pascal version
web generates the website
profile profile the Nimrod compiler
csource build the C sources for installation
zip build the installation ZIP package
inno build the Inno Setup installer
""" % (NIMROD_VERSION + ' ' * (44-len(NIMROD_VERSION)), sys.version)
def main(args):
if len(args) == 0:
print HELP
else:
i = 0
while args[i][:1] == "-":
a = args[i]
if a in ("--force", "-f", "-B", "-b"):
global force
force = True
elif a in ("-h", "--help", "-?"):
print HELP
return
elif a == "--diff":
global GENERATE_DIFF
GENERATE_DIFF = True
elif a == "--no_fpc":
global USE_FPC
USE_FPC = False
else:
Error("illegal option: " + a)
i = i + 1
cmd = args[i]
if cmd == "rod": cmd_rod(join(args[i+1:]))
elif cmd == "nim": cmd_nim()
elif cmd == "clean": cmd_clean()
elif cmd == "boot": cmd_boot(join(args[i+1:]))
elif cmd == "rodsrc": cmd_rodsrc()
elif cmd == "web": cmd_web()
elif cmd == "profile": cmd_profile()
elif cmd == "zip": cmd_zip()
elif cmd == "inno": cmd_inno()
elif cmd == "csource": cmd_csource()
else: Error("illegal command: " + cmd)
def cmd_csource():
Exec("nimrod cc -r tools/niminst --var:version=%s csource rod/nimrod" %
NIMROD_VERSION)
def cmd_zip():
Exec("nimrod cc -r tools/niminst --var:version=%s zip rod/nimrod" %
NIMROD_VERSION)
def cmd_inno():
Exec("nimrod cc -r tools/niminst --var:version=%s inno rod/nimrod" %
NIMROD_VERSION)
# -------------------------- bootstrap ----------------------------------------
def readCFiles():
result = {}
if GENERATE_DIFF:
for f in Glob("rod/nimcache/rod/*.c") + Glob("rod/nimcache/lib/*.c"):
x = os.path.split(f)[1]
result[x] = open(f).readlines()[1:]
return result
def genBootDiff(genA, genB):
def interestingDiff(a, b):
#a = re.sub(r"([a-zA-Z_]+)([0-9]+)", r"\1____", a)
#b = re.sub(r"([a-zA-Z_]+)([0-9]+)", r"\1____", b)
return a != b
BOOTLOG = "bootdiff.log"
result = False
for f in Glob("diff/*.c"): Remove(f)
if Exists(BOOTLOG): Remove(BOOTLOG)
if GENERATE_DIFF:
lines = [] # lines of the generated logfile
if len(genA) != len(genB): Warn("number of generated files differ!")
for filename, acontent in genA.iteritems():
bcontent = genB[filename]
if bcontent != acontent:
lines.append("------------------------------------------------------")
lines.append(filename + " differs")
# write the interesting lines to the log file:
for i in range(min(len(acontent), len(bcontent))):
la = acontent[i][:-1] # without newline!
lb = bcontent[i][:-1]
if interestingDiff(la, lb):
lines.append("%6d - %s" % (i, la))
lines.append("%6d + %s" % (i, lb))
if len(acontent) > len(bcontent):
cont = acontent
marker = "-"
else:
cont = bcontent
marker = "+"
for i in range(min(len(acontent), len(bcontent)), len(cont)):
lines.append("%6d %s %s" % (i, marker, cont[i]))
open(os.path.join("diff", "a_"+filename), "w+").write(join(acontent, ""))
open(os.path.join("diff", "b_"+filename), "w+").write(join(bcontent, ""))
if lines: result = True
open(BOOTLOG, "w+").write(join(lines, "\n"))
return result
def cmd_rodsrc():
"converts the src/*.pas files into Nimrod syntax"
PAS_FILES_BLACKLIST = split("""nsystem nmath nos ntime strutils""")
if USE_FPC and detect("fpc -h"):
cmd_nim()
compiler = "nim"
else:
compiler = "nimrod"
CMD = "%s boot --skip_proj_cfg -o:rod/%s.nim nim/%s"
result = False
for fi in Glob("nim/*.pas"):
f = FilenameNoExt(fi)
if f in PAS_FILES_BLACKLIST: continue
c = Changed(f+"__rodsrc", fi, EXPLAIN)
if c.check() or force:
Exec(CMD % (compiler, f, f+".pas"))
Exec("%s parse rod/%s.nim" % (compiler, f))
c.success()
result = True
return result
def moveExes():
Move(ExeExt("rod/nimrod"), ExeExt("bin/nimrod"))
def cmd_boot(args):
def myExec(compiler, args=args):
Exec(BOOTCMD % (compiler, args))
# some C compilers (PellesC) output the executable to the
# wrong directory. We work around this bug here:
if Exists(ExeExt("rod/nimcache/nimrod")):
Move(ExeExt("rod/nimcache/nimrod"), ExeExt("rod/nimrod"))
writePlatdefC(getNimrodPath())
d = detect("fpc -h")
if USE_FPC and d:
Echo("'%s' detected" % d)
cmd_nim()
compiler = "nim"
else:
compiler = "nimrod"
cmd_rodsrc() # regenerate nimrod version of the files
# move the new executable to bin directory (is done by cmd_rod())
# use the new executable to compile the files in the bootstrap directory:
myExec(compiler)
genA = readCFiles() # first generation of generated C files
# move the new executable to bin directory:
moveExes()
# compile again and compare:
myExec("nimrod")
genB = readCFiles() # second generation of generated C files
diff = genBootDiff(genA, genB)
if diff:
Warn("generated C files are not equal: cycle once again...")
# check if the executables are the same (they should!):
if FileCmp(Path(ExeExt("rod/nimrod")),
Path(ExeExt("bin/nimrod"))):
Echo("executables are equal: SUCCESS!")
else:
Echo("executables are not equal: cycle once again...")
diff = True
if diff:
# move the new executable to bin directory:
moveExes()
# use the new executable to compile Nimrod:
myExec("nimrod")
if FileCmp(Path(ExeExt("rod/nimrod")),
Path(ExeExt("bin/nimrod"))):
Echo("executables are equal: SUCCESS!")
else:
Warn("executables are still not equal")
# ------------------ profile --------------------------------------------------
def cmd_profile():
Exec(BOOTCMD % ("nimrod", "-d:release --profiler:on"))
moveExes()
Exec(BOOTCMD % ("nimrod", "--compile_only"))
# ------------------ web ------------------------------------------------------
def cmd_web():
Exec("nimrod cc -r tools/nimweb.nim web/nimrod --putenv:nimrodversion=%s"
% NIMROD_VERSION)
# -----------------------------------------------------------------------------
def getVersion():
return NIMROD_VERSION
# ------------------------------ clean ----------------------------------------
CLEAN_EXT = "ppu o obj dcu ~pas ~inc ~dsk ~dpr map tds err bak pyc exe rod"
def cmd_clean(dir = "."):
L = []
for x in split(CLEAN_EXT):
L.append(r".*\."+ x +"$")
extRegEx = re.compile(join(L, "|"))
if Exists("koch.dat"): Remove("koch.dat")
for f in Glob("*.pdb"): Remove(f)
for f in Glob("*.idb"): Remove(f)
for f in Glob("web/*.html"): Remove(f)
for f in Glob("doc/*.html"): Remove(f)
for f in Glob("rod/*.nim"): Remove(f) # remove generated source code
def visit(extRegEx, dirname, names):
if os.path.split(dirname)[1] == "nimcache":
shutil.rmtree(path=dirname, ignore_errors=True)
del names
else:
for name in names:
x = os.path.join(dirname, name)
if os.path.isdir(x): continue
if (extRegEx.match(name)
or (os.path.split(dirname)[1] == "tests" and ('.' not in name))):
if find(x, "/dist/") < 0 and find(x, "\\dist\\") < 0:
Echo("removing: " + x)
Remove(x)
os.path.walk(dir, visit, extRegEx)
def getHost():
# incomplete list that sys.platform may return:
# win32 aix3 aix4 atheos beos5 darwin freebsd2 freebsd3 freebsd4 freebsd5
# freebsd6 freebsd7 generic irix5 irix6 linux2 mac netbsd1 next3 os2emx
# riscos sunos5 unixware7
x = replace(lower(re.sub(r"[0-9]+$", r"", sys.platform)), "-", "")
if x == "win": return "windows"
elif x == "darwin": return "macosx" # probably Mac OS X
elif x == "sunos": return "solaris"
else: return x
def mydirwalker(dir, L):
for name in os.listdir(dir):
path = os.path.join(dir, name)
if os.path.isdir(path):
mydirwalker(path, L)
else:
L.append(path)
# --------------- install target ----------------------------------------------
def getOSandProcessor():
host = getHost()
if host == "windows": processor = "i386" # BUGFIX
else: processor = os.uname()[4]
if lower(processor) in ("i686", "i586", "i468", "i386"):
processor = "i386"
if lower(processor) in ("x86_64", "x86-64", "amd64"):
processor = "amd64"
if find(lower(processor), "sparc") >= 0:
processor = "sparc"
return (host, processor)
def writePlatdefC(nimrodpath):
import os
host, processor = getOSandProcessor()
f = open(os.path.join(nimrodpath, "build/platdef.c"), "w+")
f.write('/* Generated by koch.py */\n'
'char* nimOS(void) { return "%s"; }\n'
'char* nimCPU(void) { return "%s"; }\n'
'\n' % (host, processor))
f.close()
def detect(cmd, lookFor="version"):
try:
pipe = os.popen4(cmd)[1]
except AttributeError:
pipe = os.popen(cmd)
result = None
for line in pipe.readlines():
if find(lower(line), lookFor) >= 0:
result = line[:-1]
break
pipe.close()
if not result:
# don't give up yet; it may have written to stderr
if os.system(cmd) == 0:
result = cmd
return result
def getNimrodPath():
if os.name == "posix":
# Does not work 100% reliably. It is the best solution though.
p = replace(sys.argv[0], "./", "")
return os.path.split(os.path.join(os.getcwd(), p))[0]
else: # Windows
return os.path.split(sys.argv[0])[0]
# ------------------- main ----------------------------------------------------
if __name__ == "__main__":
main(sys.argv[1:])
|