#!/usr/bin/env python3 # 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 # 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 if __name__ == "__main__": import FileGenerator else: 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() 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 + os.linesep + indentHeaders for dep in dependencies: object, headers = dep text += object + ":" for header in headers: text += joinHeaders text += header if headers: text += os.linesep return text def UpdateDependencies(filepath, dependencies, comment=""): """ Write a dependencies file if different from dependencies. """ FileGenerator.UpdateFile(os.path.abspath(filepath), comment.rstrip() + os.linesep + 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)