From 393e4ca7e9d1308803da49bee667f1a874c612db Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Tue, 8 Apr 2003 05:00:51 +0000 Subject: Enhance Java support for package names that don't match the source directory hierarchy. --- doc/man/scons.1 | 28 +++++++ src/engine/SCons/Tool/javac.py | 175 ++++++++++++++++++++++++++++++++--------- test/JAVAC.py | 8 +- 3 files changed, 169 insertions(+), 42 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 410dd8d..54afbb0 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -1256,6 +1256,34 @@ env.Jar(target = 'foo.jar', source = 'classes') .IP Java Builds one or more Java class files from a source tree of .java files. +The class files will be placed underneath +the specified target directory. +SCons assumes that each .java file +contains a single public class +with the same name as the basename of the file; +that is, the file +.I Foo.java +contains a single public class named +.IR Foo . +SCons will search each Java file +for the Java package name, +which it assumes can be found on a line +beginning with the string +.B package +in the first column. +The resulting .class file +will be placed in a directory reflecting +the specified package name; +that is, +the file +.I Foo.java +with a package name of +.I sub.dir +will generate a corresponding +.IR sub/dir/Foo.class +class file. + +Example: .ES env.Java(target = 'classes', source = 'src') diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py index fff6de5..22145fc 100644 --- a/src/engine/SCons/Tool/javac.py +++ b/src/engine/SCons/Tool/javac.py @@ -33,50 +33,149 @@ selection method. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import glob import os.path +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. + """ + 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, '.')) + 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. + """ + 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 + def generate(env, platform): """Add Builders and construction variables for javac to an Environment.""" - try: - bld = env['BUILDERS']['Java'] - except KeyError: - def emit_java_files(target, source, env): - """Create and return lists of source java files - and their corresponding target class files. - """ - env['_JAVACLASSDIR'] = target[0] - 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) - java_paths = map(lambda f, d=dirname: - os.path.join(d, f), - java_files) - arg.extend(java_paths) - os.path.walk(source[0], visit, slist) - tlist = map(lambda x, t=target[0], cs=class_suffix: - os.path.join(t, x[:-5] + cs), - slist) - - return tlist, slist - - JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM', - emitter = emit_java_files, - target_factory = SCons.Node.FS.default_fs.File, - source_factory = SCons.Node.FS.default_fs.File) - - env['BUILDERS']['Java'] = JavaBuilder - - env['JAVAC'] = 'javac' - env['JAVACFLAGS'] = '' - env['JAVACCOM'] = '$JAVAC $JAVACFLAGS -d $_JAVACLASSDIR -sourcepath $_JAVASRCDIR $SOURCES' - env['JAVACLASSSUFFIX'] = '.class' - env['JAVASUFFIX'] = '.java' + def emit_java_files(target, source, env): + """Create and return lists of source java files + and their corresponding target class files. + """ + env['_JAVACLASSDIR'] = target[0] + 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) + java_paths = map(lambda f, d=dirname: + os.path.join(d, f), + 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) + if pkg_dir: + for c in classes: + tlist.append(os.path.join(target[0], + pkg_dir, + c + class_suffix)) + else: + tlist.append(os.path.join(target[0], + file[:-5] + class_suffix)) + return tlist, slist + + JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM', + emitter = emit_java_files, + target_factory = SCons.Node.FS.default_fs.File, + source_factory = SCons.Node.FS.default_fs.File) + + env['BUILDERS']['Java'] = JavaBuilder + + env['JAVAC'] = 'javac' + env['JAVACFLAGS'] = '' + env['JAVACCOM'] = '$JAVAC $JAVACFLAGS -d $_JAVACLASSDIR -sourcepath $_JAVASRCDIR $SOURCES' + env['JAVACLASSSUFFIX'] = '.class' + env['JAVASUFFIX'] = '.java' def exists(env): return env.Detect('javac') diff --git a/test/JAVAC.py b/test/JAVAC.py index 61d8d4b..9526259 100644 --- a/test/JAVAC.py +++ b/test/JAVAC.py @@ -129,7 +129,7 @@ public class Example1 """) test.write(['com', 'sub', 'foo', 'Example2.java'], """\ -package com.sub.foo; +package com.other; public class Example2 { @@ -171,7 +171,7 @@ public class Example4 """) test.write(['com', 'sub', 'bar', 'Example5.java'], """\ -package com.sub.bar; +package com.other; public class Example5 { @@ -203,10 +203,10 @@ test.run(arguments = '.') test.fail_test(test.read('wrapper.out') != "wrapper.py /usr/local/j2sdk1.3.1/bin/javac -d classes -sourcepath com/sub/bar com/sub/bar/Example4.java com/sub/bar/Example5.java com/sub/bar/Example6.java\n") test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'foo', 'Example1.class'))) -test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'foo', 'Example2.class'))) +test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'other', 'Example2.class'))) test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'foo', 'Example3.class'))) test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'bar', 'Example4.class'))) -test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'bar', 'Example5.class'))) +test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'other', 'Example5.class'))) test.fail_test(not os.path.exists(test.workpath('classes', 'com', 'sub', 'bar', 'Example6.class'))) test.up_to_date(arguments = '.') -- cgit v0.12