Created
June 14, 2013 22:47
-
-
Save catalan42/5785887 to your computer and use it in GitHub Desktop.
Groovy program to create makefile dependencies for C/C++ or PL/I using the "touch" command.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env groovy | |
import java.util.concurrent.atomic.* | |
class Depends { | |
static { | |
System.setProperty( 'line.separator', '\n' ); // unix format | |
} | |
final hash = '#' | |
final tab = '\t' | |
final filenameChrs = $/[-.a-zA-Z0-9\/]/$ | |
// A case-insensitive list of source code filename suffixes which we search | |
// for #include or %include statements. Suffixes in this list must include | |
// the "." (dot) character. | |
final suffixesToProc = [ '.c', '.cc', '.cpp', '.cxx', '.c++', | |
'.h', '.hh', '.hpp', '.hxx', '.h++', | |
'.pli', '.pl1', '.inc' ] | |
boolean isRecursive | |
def filesToProc = [] | |
def depSpecs = [] | |
def fileCount = new AtomicInteger( 0 ); | |
def showCnt = { | |
synchronized( fileCount ) { | |
if ( fileCount.get() % 20 == 0 ) { | |
if ( fileCount.get() % 1000 == 0 ) { | |
println() | |
print "${fileCount.get()}".padLeft(9) + " " | |
} | |
print '.' | |
} | |
fileCount.incrementAndGet() | |
} | |
} | |
void findFiles( File file ) { | |
file = new File( file.canonicalPath ) | |
String parentName = file.parent | |
String lowerName = file.name.toLowerCase() | |
if ( lowerName.startsWith('.') ) { | |
return // skip hidden files | |
} | |
if ( ( isRecursive ) && ( file.isDirectory() ) ) { | |
file.eachFile { child -> findFiles( child ) } | |
} else { | |
suffixesToProc.each { | |
def lowerSuffix = it.toLowerCase() | |
if ( lowerName.endsWith( lowerSuffix ) ) { | |
filesToProc << file | |
return | |
} | |
} | |
} | |
} | |
void findDeps() { | |
filesToProc.each { srcFile -> | |
def srcText = srcFile.text | |
def srcLines = srcText.readLines() | |
showCnt() | |
List depFiles = [] | |
srcLines.each { line -> | |
// C++ formatted includes: #include "filename.typ" | |
if ( line =~ /^\s*${hash}include\s+".*"/ ) { | |
def cppQuotedFile = $/"(${filenameChrs}+)"/$ | |
def matcher = ( line =~ cppQuotedFile ) | |
assert matcher.size() == 1 | |
// There should be exactly 1 match. Because we included parens in | |
// our regex, the match is a single group. | |
def group = matcher[0] | |
assert group.size() == 2 | |
// Because we had exactly 1 set of parens, the group will always be | |
// of length 2. The first element is the entire match. The 2nd | |
// element is the part inside the parens (i.e. the name of the | |
// include file). | |
def wholeMatch = group[0] | |
def inclName = group[1] | |
depFiles << inclName | |
} | |
// C++ formatted includes: #include <filename.typ> | |
if ( line =~ /^\s*${hash}include\s+<.*>/ ) { | |
def cppQuotedFile = $/<(${filenameChrs}+)>/$ | |
def matcher = ( line =~ cppQuotedFile ) | |
assert matcher.size() == 1 | |
// There should be exactly 1 match. Because we included parens in | |
// our regex, the match is a single group. | |
def group = matcher[0] | |
assert group.size() == 2 | |
// Because we had exactly 1 set of parens, the group will always be | |
// of length 2. The first element is the entire match. The 2nd | |
// element is the part inside the parens (i.e. the name of the | |
// include file). | |
def wholeMatch = group[0] | |
def inclName = group[1] | |
if (inclName.contains( '.' )) { // e.g. <string.h> | |
// Ignore 'system' includes like <iostream>, <fstream>, etc | |
depFiles << inclName | |
} | |
} | |
// PL1 formatted includes (case insensitive) | |
// %include 'filename.typ' | |
if ( line =~ /(?i)^\s*%include\s+'.*'/ ) { | |
def pliQuotedFile = $/'(${filenameChrs}+)'/$ | |
def matcher = ( line =~ pliQuotedFile ) | |
assert matcher.size() == 1 | |
// There should be exactly 1 match. Because we included parens in | |
// our regex, the match is a single group. | |
def group = matcher[0] | |
assert group.size() == 2 | |
// Because we had exactly 1 set of parens, the group will always be | |
// of length 2. The first element is the entire match. The 2nd | |
// element is the part inside the parens (i.e. the name of the | |
// include file). | |
def wholeMatch = group[0] | |
def inclName = group[1] | |
depFiles << inclName | |
} | |
} // srcLines.each | |
if ( depFiles.size() ) { // only write if file has dependencies | |
def depSpec = "${srcFile.name} :" | |
depFiles.each { | |
depSpec += " ${it}" | |
} | |
depSpecs << depSpec // target : <dependencies> | |
depSpecs << tab + '@ touch $@' // <tab> <update command> (silent) | |
} | |
} | |
} | |
void run( args ) { | |
def cli = new CliBuilder( usage:"Depends [-r|--recurse'] <files>" ) | |
cli.h( argName: 'help', longOpt:'help', 'usage information' ) | |
cli.r( argName: 'recurse', longOpt:'recurse', 'process files recursively' ) | |
def options = cli.parse( args ) | |
if (!options) { throw new RuntimeException( 'problem' ) } | |
def argFiles = options.arguments() | |
if ( (options.h) || ( !argFiles ) ) { | |
println """ | |
Builds make dependencies. | |
""" | |
cli.usage() | |
println() | |
return | |
} | |
isRecursive = (options.r) | |
argFiles.each { name -> | |
findFiles( new File( name ) ) | |
} | |
findDeps(); | |
println() | |
println() | |
println " Processed ${fileCount} files" | |
def depSpecsFile = new File('depSpecs.mk') | |
println " Saved dependency specs to: " + depSpecsFile.path | |
depSpecsFile.withWriter { out -> | |
depSpecs.each { | |
out.writeLine it | |
} | |
} | |
println() | |
} | |
static main(args) { | |
new Depends().run( args ) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment