aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/Dependencies.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/Dependencies.py')
-rw-r--r--scripts/Dependencies.py149
1 files changed, 149 insertions, 0 deletions
diff --git a/scripts/Dependencies.py b/scripts/Dependencies.py
new file mode 100644
index 000000000..86a2d3b22
--- /dev/null
+++ b/scripts/Dependencies.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# Dependencies.py - discover, read, and write dependencies file for make.
+# The format like the output from "g++ -MM" which produces a
+# list of header (.h) files used by source files (.cxx).
+# As a module, provides
+# FindPathToHeader(header, includePath) -> path
+# FindHeadersInFile(filePath) -> [headers]
+# FindHeadersInFileRecursive(filePath, includePath, renames) -> [paths]
+# FindDependencies(sourceGlobs, includePath, objExt, startDirectory, renames) -> [dependencies]
+# ExtractDependencies(input) -> [dependencies]
+# TextFromDependencies(dependencies)
+# WriteDependencies(output, dependencies)
+# UpdateDependencies(filepath, dependencies)
+# PathStem(p) -> stem
+# InsertSynonym(dependencies, current, additional) -> [dependencies]
+# If run as a script reads from stdin and writes to stdout.
+# Only tested with ASCII file names.
+# Copyright 2019 by Neil Hodgson <neilh@scintilla.org>
+# The License.txt file describes the conditions under which this software may be distributed.
+# Requires Python 2.7 or later
+
+import codecs, glob, os, sys
+
+from . import FileGenerator
+
+continuationLineEnd = " \\"
+
+def FindPathToHeader(header, includePath):
+ for incDir in includePath:
+ relPath = os.path.join(incDir, header)
+ if os.path.exists(relPath):
+ return relPath
+ return ""
+
+fhifCache = {} # Remember the includes in each file. ~5x speed up.
+def FindHeadersInFile(filePath):
+ if filePath not in fhifCache:
+ headers = []
+ with codecs.open(filePath, "r", "utf-8") as f:
+ for line in f:
+ if line.strip().startswith("#include"):
+ parts = line.split()
+ if len(parts) > 1:
+ header = parts[1]
+ if header[0] != '<': # No system headers
+ headers.append(header.strip('"'))
+ fhifCache[filePath] = headers
+ return fhifCache[filePath]
+
+def FindHeadersInFileRecursive(filePath, includePath, renames):
+ headerPaths = []
+ for header in FindHeadersInFile(filePath):
+ if header in renames:
+ header = renames[header]
+ relPath = FindPathToHeader(header, includePath)
+ if relPath and relPath not in headerPaths:
+ headerPaths.append(relPath)
+ subHeaders = FindHeadersInFileRecursive(relPath, includePath, renames)
+ headerPaths.extend(sh for sh in subHeaders if sh not in headerPaths)
+ return headerPaths
+
+def RemoveStart(relPath, start):
+ if relPath.startswith(start):
+ return relPath[len(start):]
+ return relPath
+
+def ciKey(f):
+ return f.lower()
+
+def FindDependencies(sourceGlobs, includePath, objExt, startDirectory, renames={}):
+ deps = []
+ for sourceGlob in sourceGlobs:
+ sourceFiles = glob.glob(sourceGlob)
+ # Sorting the files minimizes deltas as order returned by OS may be arbitrary
+ sourceFiles.sort(key=ciKey)
+ for sourceName in sourceFiles:
+ objName = os.path.splitext(os.path.basename(sourceName))[0]+objExt
+ headerPaths = FindHeadersInFileRecursive(sourceName, includePath, renames)
+ depsForSource = [sourceName] + headerPaths
+ depsToAppend = [RemoveStart(fn.replace("\\", "/"), startDirectory) for
+ fn in depsForSource]
+ deps.append([objName, depsToAppend])
+ return deps
+
+def PathStem(p):
+ """ Return the stem of a filename: "CallTip.o" -> "CallTip" """
+ return os.path.splitext(os.path.basename(p))[0]
+
+def InsertSynonym(dependencies, current, additional):
+ """ Insert a copy of one object file with dependencies under a different name.
+ Used when one source file is used to create two object files with different
+ preprocessor definitions. """
+ result = []
+ for dep in dependencies:
+ result.append(dep)
+ if (dep[0] == current):
+ depAdd = [additional, dep[1]]
+ result.append(depAdd)
+ return result
+
+def ExtractDependencies(input):
+ """ Create a list of dependencies from input list of lines
+ Each element contains the name of the object and a list of
+ files that it depends on.
+ Dependencies that contain "/usr/" are removed as they are system headers. """
+
+ deps = []
+ for line in input:
+ headersLine = line.startswith(" ") or line.startswith("\t")
+ line = line.strip()
+ isContinued = line.endswith("\\")
+ line = line.rstrip("\\ ")
+ fileNames = line.strip().split(" ")
+ if not headersLine:
+ # its a source file line, there may be headers too
+ sourceLine = fileNames[0].rstrip(":")
+ fileNames = fileNames[1:]
+ deps.append([sourceLine, []])
+ deps[-1][1].extend(header for header in fileNames if "/usr/" not in header)
+ return deps
+
+def TextFromDependencies(dependencies):
+ """ Convert a list of dependencies to text. """
+ text = ""
+ indentHeaders = "\t"
+ joinHeaders = continuationLineEnd + "\n" + indentHeaders
+ for dep in dependencies:
+ object, headers = dep
+ text += object + ":"
+ for header in headers:
+ text += joinHeaders
+ text += header
+ if headers:
+ text += "\n"
+ return text
+
+def UpdateDependencies(filepath, dependencies, comment=""):
+ """ Write a dependencies file if different from dependencies. """
+ FileGenerator.UpdateFile(filepath, comment+TextFromDependencies(dependencies))
+
+def WriteDependencies(output, dependencies):
+ """ Write a list of dependencies out to a stream. """
+ output.write(TextFromDependencies(dependencies))
+
+if __name__ == "__main__":
+ """ Act as a filter that reformats input dependencies to one per line. """
+ inputLines = sys.stdin.readlines()
+ deps = ExtractDependencies(inputLines)
+ WriteDependencies(sys.stdout, deps)