trimcc.nim 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # Trim C compiler installation to a minimum
  2. import strutils, os, pegs, strtabs, math, times
  3. const
  4. Essential = """gcc.exe g++.exe gdb.exe ld.exe as.exe c++.exe cpp.exe cc1.exe
  5. crtbegin.o crtend.o crt2.o dllcrt2.o libgcc_s_dw2-1.dll libgcc_s_sjlj-1.dll
  6. libgcc_s_seh-1.dll libexpat-1.dll libwinpthread-1.dll aio.h dlfcn.h fcntl.h
  7. fenv.h fmtmsg.h fnmatch.h ftw.h errno.h glob.h gtmath.h if.h in.h ipc.h
  8. langinfo.h locale.h math.h mman.h netdb.h nl_types.h poll.h pthread.h pwd.h
  9. sched.h select.h semaphore.h signal.h socket.h spawn.h stat.h statvfs.h stdio.h
  10. stdlib.h string.h strings.h tcp.h time.h types.h ucontext.h uio.h utsname.h
  11. unistd.h wait.h varargs.h windows.h zlib.h
  12. """.split
  13. proc includes(headerpath, headerfile: string, whitelist: StringTableRef) =
  14. whitelist[headerfile] = "processed"
  15. for line in lines(headerpath):
  16. if line =~ peg"""s <- ws '#include' ws ('"' / '<') {[^">]+} ('"' / '>') ws
  17. comment <- '/*' @ '*/' / '//' .*
  18. ws <- (comment / \s+)* """:
  19. let m = matches[0].extractFilename
  20. if whitelist.getOrDefault(m) != "processed":
  21. whitelist[m] = "found"
  22. proc processIncludes(dir: string, whitelist: StringTableRef) =
  23. for kind, path in walkDir(dir):
  24. case kind
  25. of pcFile:
  26. let name = extractFilename(path)
  27. if ('.' notin name and "include" in path) or ("c++" in path):
  28. let n = whitelist.getOrDefault(name)
  29. if n != "processed": whitelist[name] = "found"
  30. if name.endsWith(".h"):
  31. let n = whitelist.getOrDefault(name)
  32. if n == "found": includes(path, name, whitelist)
  33. of pcDir: processIncludes(path, whitelist)
  34. else: discard
  35. proc gatherFiles(dir: string, whitelist: StringTableRef, result: var seq[string]) =
  36. for kind, path in walkDir(dir):
  37. case kind
  38. of pcFile:
  39. let name = extractFilename(path)
  40. if not whitelist.hasKey(name):
  41. result.add(path)
  42. of pcDir:
  43. gatherFiles(path, whitelist, result)
  44. else:
  45. discard
  46. proc gatherEmptyFolders(dir: string, whitelist: StringTableRef, result: var seq[string]) =
  47. var empty = true
  48. for kind, path in walkDir(dir):
  49. case kind
  50. of pcFile:
  51. empty = false
  52. of pcDir:
  53. let (none, name) = splitPath(path)
  54. if not whitelist.hasKey(name):
  55. gatherEmptyFolders(path, whitelist, result)
  56. empty = false
  57. else:
  58. discard
  59. if empty:
  60. result.add(dir)
  61. proc newName(f: string): string =
  62. let (dir, name, ext) = splitFile(f)
  63. return dir / "trim_" & name & ext
  64. proc ccStillWorks(): bool =
  65. const
  66. c1 = r"nim c --verbosity:0 --force_build koch"
  67. c2 = r"nim c --verbosity:0 --force_build --threads:on --out:tempOne.exe tools/trimcc"
  68. c3 = r"nim c --verbosity:0 --force_build --threads:on --out:tempTwo.exe tools/fakeDeps"
  69. c4 = r".\koch.exe"
  70. c5 = r".\tempOne.exe"
  71. c6 = r".\tempTwo.exe"
  72. result = execShellCmd(c1) == 0 and execShellCmd(c2) == 0 and
  73. execShellCmd(c3) == 0 and execShellCmd(c4) == 0 and
  74. execShellCmd(c5) == 0 and execShellCmd(c6) == 0
  75. proc trialDeletion(files: seq[string], a, b: int, whitelist: StringTableRef): bool =
  76. result = true
  77. var single = (a == min(b, files.high))
  78. for path in files[a .. min(b, files.high)]:
  79. try:
  80. moveFile(dest=newName(path), source=path)
  81. except OSError:
  82. return false
  83. # Test if compilation still works, even with the moved files.
  84. if ccStillWorks():
  85. for path in files[a .. min(b, files.high)]:
  86. try:
  87. removeFile(newName(path))
  88. echo "Optional: ", path
  89. except OSError:
  90. echo "Warning, couldn't move ", path
  91. moveFile(dest=path, source=newName(path))
  92. return false
  93. else:
  94. for path in files[a .. min(b, files.high)]:
  95. echo "Required: ", path
  96. moveFile(dest=path, source=newName(path))
  97. if single:
  98. whitelist[path] = "found"
  99. result = false
  100. proc main(dir: string) =
  101. # Construct a whitelist of files to not remove
  102. var whitelist = newStringTable(modeCaseInsensitive)
  103. for e in Essential:
  104. whitelist[e] = "found"
  105. while true:
  106. let oldLen = whitelist.len
  107. processIncludes(dir, whitelist)
  108. if oldLen == whitelist.len:
  109. break
  110. # Remove batches of files
  111. var nearlyDone = false
  112. while true:
  113. # Gather files to test
  114. var allFiles = newSeq[string]()
  115. gatherFiles(dir, whitelist, allFiles)
  116. # Determine the initial size of groups to check
  117. var
  118. maxBucketSize = len(allFiles)
  119. bucketSize = 1
  120. # Loop through the list of files, deleting batches
  121. var i = 0
  122. while i < allFiles.len:
  123. var success = trialDeletion(allFiles, i, i+bucketSize-1, whitelist)
  124. inc i, bucketSize
  125. # If we aren't on the last pass, adjust the batch size based on success
  126. if not nearlyDone:
  127. if success:
  128. bucketSize = min(bucketSize * 2, maxBucketSize)
  129. else:
  130. bucketSize = max(bucketSize div 2, 1)
  131. echo "Bucket size is now ", bucketSize
  132. # After looping through all the files, check if we need to break.
  133. if nearlyDone:
  134. break
  135. if bucketSize == 1:
  136. nearlyDone = true
  137. while true:
  138. var
  139. emptyFolders = newSeq[string]()
  140. changed = false
  141. gatherEmptyFolders(dir, whitelist, emptyFolders)
  142. for path in emptyFolders:
  143. removeDir(path)
  144. if not ccStillWorks():
  145. createDir(path)
  146. whitelist[path] = "found"
  147. else:
  148. changed = true
  149. if not changed:
  150. break
  151. if paramCount() == 1:
  152. doAssert ccStillWorks()
  153. main(paramStr(1))
  154. else:
  155. quit "Usage: trimcc c_compiler_directory", QuitSuccess