diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CHANGES.txt | 5 | ||||
-rw-r--r-- | src/engine/SCons/Tool/javac.py | 196 |
2 files changed, 115 insertions, 86 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 78a209e..e982d73 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -34,6 +34,11 @@ RELEASE 0.14 - XXX - Add an optional sort function argument to the GenerateHelpText() Options function. + From Charles Crain: + + - Parse the source .java files for class names (including inner class + names) to figure out the target .class files that will be created. + From Steven Knight: - Add support for Java (javac and jar). diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py index e2d4ecb..60233b3 100644 --- a/src/engine/SCons/Tool/javac.py +++ b/src/engine/SCons/Tool/javac.py @@ -34,107 +34,123 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path +import re import string import SCons.Builder -# Okay, I don't really know what configurability would be good for -# parsing Java files for package and/or class names, but it was easy, so -# here it is. -# -# Set java_parsing to the following values to enable three different -# flavors of parsing: -# -# 0 The file isn't actually parsed, so this will be quickest. The -# package + class name is assumed to be the file path name, and we -# just split the path name. This breaks if a package name will -# ever be different from the path to the .java file. -# -# 1 The file is read to find the package name, after which we stop. -# This should be pretty darn quick, and allows flexibility in -# package names, but assumes that the public class name in the -# file matches the file name. This seems to be a good assumption -# because, for example, if you try to declare a public class -# with a different name from the file, javac tells you: -# -# class Foo is public, should be declared in a file named Foo.java -# -# 2 Full flexibility of class names. We parse for the package name -# (like level #1) but the output .class file name is assumed to -# match the declared public class name--and, as a bonus, this will -# actually support multiple public classes in a single file. My -# guess is that's illegal Java, though... Or is it an option -# supported by some compilers? -# java_parsing = 1 -if java_parsing == 0: - def parse_java(file, suffix): - """ "Parse" a .java file. - - This actually just splits the file name, so the assumption here - is that the file name matches the public class name, and that - the path to the file is the same as the package name. - """ - return os.path.split(file) -elif java_parsing == 1: - def parse_java(file, suffix): - """Parse a .java file for a package name. - - This, of course, is not full parsing of Java files, but - simple-minded searching for the usual begins-in-column-1 - "package" string most Java programs use to define their package. - """ +if java_parsing: + # Parse Java files for class names. + # + # This is a really simple and cool parser from Charles Crain + # that finds appropriate class names in Java source. + + _reToken = re.compile(r'[^\\]([\'"])|([\{\}])|' + + r'(?:^|[\{\}\s;])((?:class|interface)'+ + r'\s+[A-Za-z_]\w*)|' + + r'(new\s+[A-Za-z_]\w*\s*\([^\)]*\)\s*\{)|' + + r'(//[^\r\n]*)|(/\*|\*/)') + + class OuterState: + def __init__(self): + self.listClasses = [] + self.listOutputs = [] + self.stackBrackets = [] + self.brackets = 0 + self.nextAnon = 1 + + def parseToken(self, token): + #print token + if token[:2] == '//': + pass # ignore comment + elif token == '/*': + return IgnoreState('*/', self) + elif token == '{': + self.brackets = self.brackets + 1 + elif token == '}': + self.brackets = self.brackets - 1 + if len(self.stackBrackets) and \ + self.brackets == self.stackBrackets[-1]: + self.listOutputs.append(string.join(self.listClasses, '$')) + self.listClasses.pop() + self.stackBrackets.pop() + elif token == '"': + return IgnoreState('"', self) + elif token == "'": + return IgnoreState("'", self) + elif token[:3] == "new": + # anonymous inner class + if len(self.listClasses) > 0: + clazz = self.listClasses[0] + self.listOutputs.append('%s$%d' % (clazz, self.nextAnon)) + self.brackets = self.brackets + 1 + self.nextAnon = self.nextAnon + 1 + elif token[:5] == 'class': + if len(self.listClasses) == 0: + self.nextAnon = 1 + self.listClasses.append(string.join(string.split(token[6:]))) + self.stackBrackets.append(self.brackets) + elif token[:9] == 'interface': + if len(self.listClasses) == 0: + self.nextAnon = 1 + self.listClasses.append(string.join(string.split(token[10:]))) + self.stackBrackets.append(self.brackets) + return self + + class IgnoreState: + def __init__(self, ignore_until, old_state): + self.ignore_until = ignore_until + self.old_state = old_state + def parseToken(self, token): + if token == self.ignore_until: + return self.old_state + return self + + def parse_java(file): + contents = open(file, 'r').read() + + # Is there a more efficient way to do this than to split + # the contents like this? pkg_dir = None - classes = [] - f = open(file, "rb") - while 1: - line = f.readline() - if not line: - break + for line in string.split(contents, "\n"): if line[:7] == 'package': pkg = string.split(line)[1] if pkg[-1] == ';': pkg = pkg[:-1] pkg_dir = apply(os.path.join, string.split(pkg, '.')) - classes = [ os.path.split(file[:-len(suffix)])[1] ] break - f.close() - return pkg_dir, classes - -elif java_parsing == 2: - import re - pub_re = re.compile('^\s*public(\s+abstract)?\s+class\s+(\S+)') - def parse_java(file, suffix): - """Parse a .java file for package name and classes. - - This, of course, is not full parsing of Java files, but - simple-minded searching for the usual strings most Java programs - seem to use for packages and public class names. + + initial = OuterState() + currstate = initial + for matches in _reToken.findall(contents): + # The regex produces a bunch of groups, but only one will + # have anything in it. + token = filter(lambda x: x, matches)[0] + currstate = currstate.parseToken(token) + + return pkg_dir, initial.listOutputs + +else: + # Don't actually parse Java files for class names. + # + # We might make this a configurable option in the future if + # Java-file parsing takes too long (although it shouldn't relative + # to how long the Java compiler itself seems to take...). + + def parse_java(file): + """ "Parse" a .java file. + + This actually just splits the file name, so the assumption here + is that the file name matches the public class name, and that + the path to the file is the same as the package name. """ - pkg_dir = None - classes = [] - f = open(file, "rb") - while 1: - line = f.readline() - if not line: - break - if line[:7] == 'package': - pkg = string.split(line)[1] - if pkg[-1] == ';': - pkg = pkg[:-1] - pkg_dir = apply(os.path.join, string.split(pkg, '.')) - elif line[:6] == 'public': - c = pub_re.findall(line) - try: - classes.append(c[0][1]) - except IndexError: - pass - f.close() - return pkg_dir, classes + return os.path.split(file) def generate(env): """Add Builders and construction variables for javac to an Environment.""" + def emit_java_files(target, source, env): """Create and return lists of source java files and their corresponding target class files. @@ -143,6 +159,7 @@ def generate(env): env['_JAVASRCDIR'] = source[0] java_suffix = env.get('JAVASUFFIX', '.java') class_suffix = env.get('JAVACLASSSUFFIX', '.class') + slist = [] def visit(arg, dirname, names, js=java_suffix): java_files = filter(lambda n, js=js: n[-len(js):] == js, names) @@ -151,17 +168,24 @@ def generate(env): java_files) arg.extend(java_paths) os.path.walk(source[0], visit, slist) + tlist = [] for file in slist: - pkg_dir, classes = parse_java(file, java_suffix) + pkg_dir, classes = parse_java(file) if pkg_dir: for c in classes: tlist.append(os.path.join(target[0], pkg_dir, c + class_suffix)) + elif classes: + for c in classes: + tlist.append(os.path.join(target[0], c + class_suffix)) else: + # This is an odd end case: no package and no classes. + # Just do our best based on the source file name. tlist.append(os.path.join(target[0], - file[:-5] + class_suffix)) + file[:-len(java_suffix)] + class_suffix)) + return tlist, slist JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM', |