Vitaly,

Interestingly I have also been working on dependency tracking and multiple
source paths.

For multiple source paths, I submitted a patch for multiple source paths a
while back. I chose to use a comma to separate the source paths since it
seemed likely to offend the Windows folks and the Unix folks equally :-) The
patch was not applied because of the use of that comma. There was also some
desire to support elements instead of just an attribute, something like
  <javac>
    <srcdir name="xyz">
    <srcdir name="abc">
  </javac>

I have changed my approach now to use the more "forgiving" ways of
Project.translatePath. I have added a method to Project called
translatePathInternal. Whereas translatePath will translate a path to the
local platform conventions, this new method translates paths, whether
specified with ":/" or ";\", to a platform independent format based on ':'
and '/' but supporting DOS drive style paths (C:\...). This path can then be
tokenized on the ':' character to build the source paths vector. So you can
specify the path in either Windows or Unix style and it should work on
either platform. I'm not sure how other platforms react :-) I still haven't
added the element approach yet.


For dependency tracking I took a different approach from you. I separated
dependency analysis into a new task <depend>. The depend task builds a
dependency file by analysing the given set of class files (either a
directory or a jar). Javac uses the dependency file to determine which
classes are affected by classes being recompiled. In theory the dependency
file could be built in some other way but still be used by Javac. The
dependency format is pretty simple and could be changed easily.

Example usage
 <javac srcdir="${src1.dir}:${src2.dir};${src3.dir}"
           destdir="${build.classes}"
           classpath="${build.classpath}"
           debug="on"
           deprecation="on"
           depfile="${build.dir}/depfile.txt">
 </javac>
 <depend src="${build.classes}" depfile="${build.dir}/depfile.txt" />

I also only follow direct relationships. Say you have a scenario of A
depends on B and B depends on C but A does not depend on C. If you change C,
my approach will only compile C and B but not A. My feeling is that if A
does not depend directly on C, changes to C cannot affect A through B. I'm
still thinking about that :-)

I can't really say which approach is better. I include my source code (and a
set of diffs) here to let people see an alternative approach.


Cheers

--
Conor MacNeill
[EMAIL PROTECTED]

<<attachment: src.zip>>

? src/main/org/apache/tools/ant/taskdefs/depend
? src/main/org/apache/tools/ant/taskdefs/Depend.java
Index: build.xml
===================================================================
RCS file: /home/cvspublic/jakarta-ant/build.xml,v
retrieving revision 1.21
Index: src/main/org/apache/tools/ant/Project.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/Project.java,v
retrieving revision 1.19
diff -u -r1.19 Project.java
--- src/main/org/apache/tools/ant/Project.java  2000/04/28 08:45:21     1.19
+++ src/main/org/apache/tools/ant/Project.java  2000/05/11 14:06:58
@@ -482,23 +482,21 @@
     }
 
     /**
-        Translate a path into its native (platform specific)
-        path. This should be extremely fast, code is
-        borrowed from ECS project.
-        <p>
-        All it does is translate the : into ; and / into \
-        if needed. In other words, it isn't perfect.
-
-        @returns translated string or empty string if to_process is null or 
empty
-        @author Jon S. Stevens <a href="mailto:[EMAIL PROTECTED]">[EMAIL 
PROTECTED]</a>
-    */
-    public String translatePath(String to_process) {
+     * Translate a path according to the given path and file separators.
+     *
+     * This method is very accomodating, accepting either ':' or ';' as 
+     * path separators and replacing with the given path separator. 
+     * Similarly '/' and '\' are accepted as file separators and replaced
+     * with the given file spearator.
+     *
+     * DOS style drive specs are accomodated and passed thrugh unscathed.
+     */
+    private String translatePath(String to_process, String pathSeparator,
+                                 String fileSeparator) {
         if ( to_process == null || to_process.length() == 0 ) return "";
 
         StringBuffer bs = new StringBuffer(to_process.length() + 50);
         StringCharacterIterator sci = new StringCharacterIterator(to_process);
-        String path = System.getProperty("path.separator");
-        String file = System.getProperty("file.separator");
         String tmp = null;
         for (char c = sci.first(); c != CharacterIterator.DONE; c = 
sci.next()) {
             tmp = String.valueOf(c);
@@ -508,17 +506,44 @@
                 // if followed by a backslash, assume it is a drive
                 c = sci.next();
                 tmp = String.valueOf(c);
-                bs.append( tmp.equals("\\") ? ":" : path );
+                bs.append( tmp.equals("\\") ? ":" : pathSeparator );
                 if (c == CharacterIterator.DONE) break;
             }
 
             if (tmp.equals(":") || tmp.equals(";"))
-                tmp = path;
+                tmp = pathSeparator;
             else if (tmp.equals("/") || tmp.equals ("\\"))
-                tmp = file;
+                tmp = fileSeparator;
             bs.append(tmp);
         }
         return(bs.toString());
+    }
+
+
+    /**
+        Translate a path into its native (platform specific)
+        path. This should be extremely fast, code is
+        borrowed from ECS project.
+        <p>
+        All it does is translate the : into ; and / into \
+        if needed. In other words, it isn't perfect.
+
+        @returns translated string or empty string if to_process is null or 
empty
+        @author Jon S. Stevens <a href="mailto:[EMAIL PROTECTED]">[EMAIL 
PROTECTED]</a>
+    */
+    public String translatePath(String to_process) {
+        return translatePath(to_process, System.getProperty("path.separator"),
+                                         System.getProperty("file.separator"));
+    }
+
+    /**
+     * Translate a path from the native platform into the internal platform
+     * independent representation. The internal representation uses the ':'
+     * character as the path separator and the '/' character as the file
+     * separator. This code handles DOS style drive specifications.
+     */
+    public String translatePathInternal(String to_process) {
+        return translatePath(to_process, ":","/");
     }
 
     /**
Index: src/main/org/apache/tools/ant/taskdefs/Javac.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/Javac.java,v
retrieving revision 1.11
diff -u -r1.11 Javac.java
--- src/main/org/apache/tools/ant/taskdefs/Javac.java   2000/05/10 23:01:07     
1.11
+++ src/main/org/apache/tools/ant/taskdefs/Javac.java   2000/05/11 14:06:59
@@ -55,10 +55,12 @@
 package org.apache.tools.ant.taskdefs;
 
 import org.apache.tools.ant.*;
+import org.apache.tools.ant.taskdefs.depend.*;
 
 import java.io.*;
 import java.util.*;
 
+
 /**
  * Task to compile Java source files. This task can take the following
  * arguments:
@@ -86,7 +88,7 @@
 
 public class Javac extends MatchingTask {
 
-    private File srcDir;
+    private Vector srcDirs;
     private File destDir;
     private String compileClasspath;
     private boolean debug = false;
@@ -96,15 +98,40 @@
     private String target;
     private String bootclasspath;
     private String extdirs;
+    private File dependencyFile;
 
-    protected Vector compileList = new Vector();
+    protected Hashtable compileList = new Hashtable();
     protected Hashtable filecopyList = new Hashtable();
 
     /**
      * Set the source dir to find the source Java files.
      */
-    public void setSrcdir(String srcDirName) {
-        srcDir = project.resolveFile(srcDirName);
+    public void setSrcdir(String srcDirList) {
+        // translate the path to an internal format
+        String path = project.translatePathInternal(srcDirList);
+        
+        // use a string tokenizer to get the list of directories
+        StringTokenizer tokenizer = new StringTokenizer(path, ":", false);
+        srcDirs = new Vector();            
+        while (tokenizer.hasMoreTokens()) {
+            String component = tokenizer.nextToken().trim();
+            if (component.length() == 1) {
+                // small path component, could be a DOS drive.
+                // get the next token and check if it starts
+                // with a backslash.
+                if (tokenizer.hasMoreTokens()) {
+                    String nextComponent = tokenizer.nextToken().trim();
+                    if (nextComponent.startsWith("\\")) {
+                        component += "\\" + nextComponent;
+                    }
+                    else {
+                        srcDirs.addElement(project.resolveFile(component));
+                        component = nextComponent;
+                    }
+                }
+            }
+            srcDirs.addElement(project.resolveFile(component));
+        }
     }
 
     /**
@@ -175,16 +202,99 @@
     }
 
     /**
+     * Set the dependency file to use.
+     */
+    public void setDepfile(String filename) {
+        this.dependencyFile = new File(filename);
+    }
+
+    /**
+     * Add all files which directly depend on the files to be compiled to the 
+     * list of files.
+     *
+     * At this time, transitive dependency relationships are not followed.
+     */
+    private void addDependencies(Hashtable compileList, File dependencyFile) 
throws IOException {
+        // build a dependency map
+        FileReader fr = null;
+        BufferedReader reader = null;
+        try {
+            fr = new FileReader(dependencyFile);
+            reader = new BufferedReader(fr);
+        }
+        catch (IOException e) {
+            project.log("Unable to open dependency file - ignoring");
+            return;
+        }
+        
+        Hashtable dependentFilesList = new Hashtable();
+        
+        String line = null;
+        String dependentClass = null;
+        boolean skipping = true;
+        while ((line = reader.readLine()) != null) {
+            int index = line.indexOf(':');
+            if (index != -1) {
+                // its a dependency line
+                dependentClass = line.substring(0, index).trim();
+                int innerClassIndex = dependentClass.indexOf("$");
+                
+                if (innerClassIndex != -1) {
+                    dependentClass = dependentClass.substring(0, 
innerClassIndex);
+                }
+                // Only look at the dependencies if this file not already in
+                // the compile list
+                if (!compileList.containsKey(dependentClass)) {
+                    skipping = false;
+                }
+            }
+            else if (!skipping) {
+                String dependency = line.trim();
+                int innerClassIndex = dependency.indexOf("$");
+                
+                if (innerClassIndex != -1) {
+                    dependency = dependency.substring(0, innerClassIndex);
+                }
+                
+                if (compileList.containsKey(dependency) && 
+                    !dependency.equals(dependentClass)) {
+                    // A file which is a dependency is being recompiled
+                    project.log("Adding " + dependentClass + " because " + 
dependency + " has changed.");
+                    String sourceFilename = 
getSourcePathForClass(dependentClass);
+                    if (sourceFilename == null) {
+                        project.log("Warning: unable to find source for class 
" + dependency);
+                    }
+                    else {
+                        dependentFilesList.put(dependentClass, sourceFilename);
+                    }
+                    skipping = true;
+                }
+            }
+        }
+        
+        // now merge the newly found files into the compile list
+        for (Enumeration keys = dependentFilesList.keys(); 
keys.hasMoreElements();) {
+            String className = (String)keys.nextElement();
+            compileList.put(className, dependentFilesList.get(className));
+        }
+    }
+    
+
+
+    /**
      * Executes the task.
      */
     public void execute() throws BuildException {
         // first off, make sure that we've got a srcdir and destdir
 
-        if (srcDir == null) {
+        if (srcDirs == null) {
             throw new BuildException("srcdir attribute must be set!");
         }
-        if (!srcDir.exists()) {
-            throw new BuildException("srcdir does not exist!");
+        for (Enumeration e = srcDirs.elements(); e.hasMoreElements(); ) {
+            File srcDir = (File)e.nextElement();
+            if (!srcDir.exists()) {
+                throw new BuildException("srcdir " + srcDir.getPath() + " does 
not exist!");
+            }
         }
         if (destDir == null) {
             throw new BuildException("destdir attribute must be set!");
@@ -193,11 +303,17 @@
         // scan source and dest dirs to build up both copy lists and
         // compile lists
 
-        DirectoryScanner ds = this.getDirectoryScanner(srcDir);
+        // go through all src directories looking for files
+        resetFileLists();
+        for (Enumeration e = srcDirs.elements(); e.hasMoreElements(); ) {
+            File srcDir = (File)e.nextElement();
+            DirectoryScanner ds = this.getDirectoryScanner(srcDir);
 
-        String[] files = ds.getIncludedFiles();
+            String[] files = ds.getIncludedFiles();
 
-        scanDir(srcDir, destDir, files);
+            scanDir(srcDir, destDir, files);
+        }
+        
 
         // compile the source files
 
@@ -211,6 +327,18 @@
         }
 
         if (compileList.size() > 0) {
+            // we have some file to compile - is there a dependency file to 
+            // get more
+            if (dependencyFile != null) {
+                try {
+                    // read in the dependencies
+                    addDependencies(compileList, dependencyFile);
+                }
+                catch (IOException ioe) {
+                    throw new BuildException(ioe.getMessage());
+                }
+            }
+            
             project.log("Compiling " + compileList.size() +
                         " source files to " + destDir);
 
@@ -247,6 +375,29 @@
     }
 
     /**
+     * Clear the compileList and the fileCopyList. 
+     */
+    protected void resetFileLists() {
+        compileList.clear();
+        filecopyList.clear();
+    }
+
+    private String getSourcePathForClass(String classDotName) {
+        for (Enumeration e = srcDirs.elements(); e.hasMoreElements(); ) {
+            File srcDir = (File)e.nextElement();
+            
+            String filename = ClassFileUtils.convertDotName(classDotName) + 
".java";
+            
+            File srcFile = new File(srcDir, filename);
+            if (srcFile.exists()) {
+                return srcFile.getAbsolutePath();
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
      * Scans the directory looking for source files to be compiled and
      * support files to be copied.  The results are returned in the
      * class variables compileList and filecopyList.
@@ -254,17 +405,15 @@
 
     protected void scanDir(File srcDir, File destDir, String files[]) {
 
-        compileList.removeAllElements();
-        filecopyList.clear();
-
         long now = (new Date()).getTime();
 
         for (int i = 0; i < files.length; i++) {
             File srcFile = new File(srcDir, files[i]);
             if (files[i].endsWith(".java")) {
-                File classFile = new File(destDir, files[i].substring(0,
-                        files[i].indexOf(".java"))
-                                                    + ".class");
+                
+                String classSlashName = 
files[i].substring(0,files[i].indexOf(".java"));
+                String className = 
ClassFileUtils.convertSlashName(classSlashName);
+                File classFile = new File(destDir, classSlashName + ".class");
 
                     if (srcFile.lastModified() > now) {
                         project.log("Warning: file modified in the future: " + 
@@ -272,7 +421,7 @@
                     }
 
                     if (srcFile.lastModified() > classFile.lastModified()) {
-                        compileList.addElement(srcFile.getAbsolutePath());
+                        compileList.put(className, srcFile.getAbsolutePath());
                     }
                 } else {
                 File destFile = new File(destDir, files[i]);
@@ -342,6 +491,19 @@
 
     }
 
+    private String getSourcePath() {
+        String sourcePath = "";
+        for (Enumeration e = srcDirs.elements(); e.hasMoreElements(); ) {
+            File srcDir = (File)e.nextElement();
+            if (sourcePath.length() != 0) {
+                sourcePath += File.pathSeparator;
+            }
+            sourcePath += srcDir.getAbsolutePath();
+        }
+        return sourcePath;
+    }
+
+
     /**
      * Peforms a copmile using the classic compiler that shipped with
      * JDK 1.1 and 1.2.
@@ -357,15 +519,16 @@
 
         argList.addElement("-d");
         argList.addElement(destDir.getAbsolutePath());
+        
         argList.addElement("-classpath");
         // Just add "sourcepath" to classpath ( for JDK1.1 )
         if (Project.getJavaVersion().startsWith("1.1")) {
             argList.addElement(classpath + File.pathSeparator +
-                               srcDir.getAbsolutePath());
+                               getSourcePath());
         } else {
             argList.addElement(classpath);
             argList.addElement("-sourcepath");
-            argList.addElement(srcDir.getAbsolutePath());
+            argList.addElement(getSourcePath());
             if (target != null) {
                 argList.addElement("-target");
                 argList.addElement(target);
@@ -463,7 +626,7 @@
         // Jikes has no option for source-path so we
         // will add it to classpath.
         classpath.append(File.pathSeparator);
-        classpath.append(srcDir.getAbsolutePath());
+        classpath.append(getSourcePath());
 
         Vector argList = new Vector();
 
Index: src/main/org/apache/tools/ant/taskdefs/defaults.properties
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/taskdefs/defaults.properties,v
retrieving revision 1.10
diff -u -r1.10 defaults.properties
--- src/main/org/apache/tools/ant/taskdefs/defaults.properties  2000/03/23 
03:40:32     1.10
+++ src/main/org/apache/tools/ant/taskdefs/defaults.properties  2000/05/11 
14:06:59
@@ -27,11 +27,14 @@
 filter=org.apache.tools.ant.taskdefs.Filter
 fixcrlf=org.apache.tools.ant.taskdefs.FixCRLF
 rename=org.apache.tools.ant.taskdefs.Rename
+depend=org.apache.tools.ant.taskdefs.Depend
 
 # optional tasks
 script=org.apache.tools.ant.taskdefs.optional.Script
 netrexxc=org.apache.tools.ant.taskdefs.optional.NetRexxC
 renameext=org.apache.tools.ant.taskdefs.optional.RenameExtensions
+ejbc=org.apache.tools.ant.taskdefs.optional.ejbc
+ddcreator=org.apache.tools.ant.taskdefs.optional.DDCreator
 
 # deprecated ant tasks (kept for back compatibility)
 javadoc2=org.apache.tools.ant.taskdefs.Javadoc

Reply via email to