summary refs log tree commit diff stats
path: root/koch.py
blob: 974fdec041c77e411a2181a9e47043c47998853f (plain) (blame)
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:])