summary refs log tree commit diff stats
path: root/tools/trimcc.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tools/trimcc.nim')
-rw-r--r--[-rwxr-xr-x]tools/trimcc.nim203
1 files changed, 174 insertions, 29 deletions
diff --git a/tools/trimcc.nim b/tools/trimcc.nim
index 966f406d4..41795e1aa 100755..100644
--- a/tools/trimcc.nim
+++ b/tools/trimcc.nim
@@ -1,29 +1,174 @@
-# Trim C compiler installation to a minimum

-

-import strutils, os

-

-proc newName(f: string): string =

-  var (dir, name, ext) = splitFile(f)

-  return dir / "trim_" & name & ext

-

-proc walker(dir: string) = 

-  for kind, path in walkDir(dir):

-    case kind

-    of pcFile:

-      moveFile(newName(path), path)

-      # test if installation still works:

-      if execShellCmd(r"nimrod c --force_build tests\tlastmod") == 0:

-        echo "Optional: ", path

-        removeFile(newName(path))

-      else:

-        echo "Required: ", path

-        # copy back:

-        moveFile(path, newName(path))

-    of pcDirectory:

-      walker(path)

-    else: nil

-

-if paramCount() == 1:

-  walker(paramStr(1))

-else:

-  echo "Usage: trimcc c_compiler_directory"

+# Trim C compiler installation to a minimum
+
+import strutils, os, pegs, strtabs, math, times
+
+const
+  Essential = """gcc.exe g++.exe gdb.exe ld.exe as.exe c++.exe cpp.exe cc1.exe
+crtbegin.o crtend.o crt2.o dllcrt2.o libgcc_s_dw2-1.dll libgcc_s_sjlj-1.dll
+libgcc_s_seh-1.dll libexpat-1.dll libwinpthread-1.dll aio.h dlfcn.h fcntl.h
+fenv.h fmtmsg.h fnmatch.h ftw.h errno.h glob.h gtmath.h if.h in.h ipc.h
+langinfo.h locale.h math.h mman.h netdb.h nl_types.h poll.h pthread.h pwd.h
+sched.h select.h semaphore.h signal.h socket.h spawn.h stat.h statvfs.h stdio.h
+stdlib.h string.h strings.h tcp.h time.h types.h ucontext.h uio.h utsname.h
+unistd.h wait.h varargs.h windows.h zlib.h
+""".split
+
+proc includes(headerpath, headerfile: string, whitelist: StringTableRef) =
+  whitelist[headerfile] = "processed"
+  for line in lines(headerpath):
+    if line =~ peg"""s <- ws '#include' ws ('"' / '<') {[^">]+} ('"' / '>') ws
+                     comment <- '/*' @ '*/' / '//' .*
+                     ws <- (comment / \s+)* """:
+      let m = matches[0].extractFilename
+      if whitelist.getOrDefault(m) != "processed":
+        whitelist[m] = "found"
+
+proc processIncludes(dir: string, whitelist: StringTableRef) =
+  for kind, path in walkDir(dir):
+    case kind
+    of pcFile:
+      let name = extractFilename(path)
+      if ('.' notin name and "include" in path) or ("c++" in path):
+        let n = whitelist.getOrDefault(name)
+        if n != "processed": whitelist[name] = "found"
+      if name.endsWith(".h"):
+        let n = whitelist.getOrDefault(name)
+        if n == "found": includes(path, name, whitelist)
+    of pcDir: processIncludes(path, whitelist)
+    else: discard
+
+proc gatherFiles(dir: string, whitelist: StringTableRef, result: var seq[string]) =
+  for kind, path in walkDir(dir):
+    case kind
+    of pcFile:
+      let name = extractFilename(path)
+      if not whitelist.hasKey(name):
+        result.add(path)
+    of pcDir:
+      gatherFiles(path, whitelist, result)
+    else:
+      discard
+
+proc gatherEmptyFolders(dir: string, whitelist: StringTableRef, result: var seq[string]) =
+  var empty = true
+  for kind, path in walkDir(dir):
+    case kind
+    of pcFile:
+      empty = false
+    of pcDir:
+      let (none, name) = splitPath(path)
+      if not whitelist.hasKey(name):
+        gatherEmptyFolders(path, whitelist, result)
+      empty = false
+    else:
+      discard
+  if empty:
+    result.add(dir)
+
+proc newName(f: string): string =
+  let (dir, name, ext) = splitFile(f)
+  return dir / "trim_" & name & ext
+
+proc ccStillWorks(): bool =
+  const
+    c1 = r"nim c --verbosity:0 --force_build koch"
+    c2 = r"nim c --verbosity:0 --force_build --threads:on --out:tempOne.exe tools/trimcc"
+    c3 = r"nim c --verbosity:0 --force_build --threads:on --out:tempTwo.exe tools/fakeDeps"
+    c4 = r".\koch.exe"
+    c5 = r".\tempOne.exe"
+    c6 = r".\tempTwo.exe"
+  result = execShellCmd(c1) == 0 and execShellCmd(c2) == 0 and
+           execShellCmd(c3) == 0 and execShellCmd(c4) == 0 and
+           execShellCmd(c5) == 0 and execShellCmd(c6) == 0
+
+proc trialDeletion(files: seq[string], a, b: int, whitelist: StringTableRef): bool =
+  result = true
+  var single = (a == min(b, files.high))
+  for path in files[a .. min(b, files.high)]:
+    try:
+      moveFile(dest=newName(path), source=path)
+    except OSError:
+      return false
+
+  # Test if compilation still works, even with the moved files.
+  if ccStillWorks():
+    for path in files[a .. min(b, files.high)]:
+      try:
+        removeFile(newName(path))
+        echo "Optional: ", path
+      except OSError:
+        echo "Warning, couldn't move ", path
+        moveFile(dest=path, source=newName(path))
+        return false
+  else:
+    for path in files[a .. min(b, files.high)]:
+      echo "Required: ", path
+      moveFile(dest=path, source=newName(path))
+      if single:
+        whitelist[path] = "found"
+      result = false
+
+proc main(dir: string) =
+  # Construct a whitelist of files to not remove
+  var whitelist = newStringTable(modeCaseInsensitive)
+  for e in Essential:
+    whitelist[e] = "found"
+  while true:
+    let oldLen = whitelist.len
+    processIncludes(dir, whitelist)
+    if oldLen == whitelist.len:
+      break
+
+  # Remove batches of files
+  var nearlyDone = false
+  while true:
+    # Gather files to test
+    var allFiles = newSeq[string]()
+    gatherFiles(dir, whitelist, allFiles)
+
+    # Determine the initial size of groups to check
+    var
+      maxBucketSize = len(allFiles)
+      bucketSize = 1
+
+    # Loop through the list of files, deleting batches
+    var i = 0
+    while i < allFiles.len:
+      var success = trialDeletion(allFiles, i, i+bucketSize-1, whitelist)
+      inc i, bucketSize
+
+      # If we aren't on the last pass, adjust the batch size based on success
+      if not nearlyDone:
+        if success:
+          bucketSize = min(bucketSize * 2, maxBucketSize)
+        else:
+          bucketSize = max(bucketSize div 2, 1)
+      echo "Bucket size is now ", bucketSize
+
+    # After looping through all the files, check if we need to break.
+    if nearlyDone:
+      break
+    if bucketSize == 1:
+      nearlyDone = true
+
+  while true:
+    var
+      emptyFolders = newSeq[string]()
+      changed = false
+
+    gatherEmptyFolders(dir, whitelist, emptyFolders)
+    for path in emptyFolders:
+      removeDir(path)
+      if not ccStillWorks():
+        createDir(path)
+        whitelist[path] = "found"
+      else:
+        changed = true
+    if not changed:
+      break
+
+if paramCount() == 1:
+  doAssert ccStillWorks()
+  main(paramStr(1))
+else:
+  quit "Usage: trimcc c_compiler_directory", QuitSuccess