Enhance Java support for package names that don't match the source directory hierarchy.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 8 Apr 2003 05:00:51 +0000 (05:00 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Tue, 8 Apr 2003 05:00:51 +0000 (05:00 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@635 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/engine/SCons/Tool/javac.py
test/JAVAC.py

index 410dd8d82d73b716a2ef12338c4fe386e1637c09..54afbb0f45f461c42196707e49dd2e8ad6c3cc06 100644 (file)
@@ -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')
index fff6de557be0f4c03fa14218918b4a495aa1be4e..22145fcf5510a2b1f3b6746fe2174614d797e4fc 100644 (file)
@@ -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')
index 61d8d4b09f340834fc4bd29562f5abfdf4625e14..9526259243ad0a3b03969a3dd9481129f9793133 100644 (file)
@@ -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 = '.')