diff mxtool/mx.py @ 7291:a748e4d44694

Truffle API to specify type-specalized Node classes; annotation processor for automatic code generation of the type-specialized Node classes during the build process
author Christian Humer <christian.humer@gmail.com>
date Fri, 21 Dec 2012 10:44:31 -0800
parents f938212e56ab
children 4974776828ec
line wrap: on
line diff
--- a/mxtool/mx.py	Fri Dec 21 10:21:24 2012 +0100
+++ b/mxtool/mx.py	Fri Dec 21 10:44:31 2012 -0800
@@ -188,14 +188,17 @@
             if not exists(s):
                 os.mkdir(s)
 
-    def all_deps(self, deps, includeLibs, includeSelf=True):
+    def all_deps(self, deps, includeLibs, includeSelf=True, includeAnnotationProcessors=False):
         """
         Add the transitive set of dependencies for this project, including
         libraries if 'includeLibs' is true, to the 'deps' list.
         """
+        childDeps = list(self.deps)
+        if includeAnnotationProcessors and hasattr(self, 'annotationProcessors') and len(self.annotationProcessors) > 0:
+            childDeps = self.annotationProcessors + childDeps
         if self in deps:
             return deps
-        for name in self.deps:
+        for name in childDeps:
             assert name != self.name
             dep = _libs.get(name, None)
             if dep is not None:
@@ -208,7 +211,7 @@
                         abort('project named ' + name + ' required by ' + self.name + ' is ignored')
                     abort('dependency named ' + name + ' required by ' + self.name + ' is not found')
                 if not dep in deps:
-                    dep.all_deps(deps, includeLibs)
+                    dep.all_deps(deps, includeLibs=includeLibs, includeAnnotationProcessors=includeAnnotationProcessors)
         if not self in deps and includeSelf:
             deps.append(self)
         return deps
@@ -464,7 +467,7 @@
             srcDirs = pop_list(attrs, 'sourceDirs')
             deps = pop_list(attrs, 'dependencies')
             ap = pop_list(attrs, 'annotationProcessors')
-            deps += ap
+            #deps += ap
             javaCompliance = attrs.pop('javaCompliance', None)
             subDir = attrs.pop('subDir', None);
             if subDir is None:
@@ -478,9 +481,6 @@
                 abort('javaCompliance property required for non-native project ' + name)
             if len(ap) > 0:
                 p.annotationProcessors = ap
-            apc = pop_list(attrs, 'annotationProcessorClasses')
-            if len(apc) > 0:
-                p.annotationProcessorClasses = apc
             p.__dict__.update(attrs)
             self.projects.append(p)
 
@@ -729,7 +729,7 @@
                     entryPath = zi.filename
                     yield zf, entryPath
 
-def sorted_deps(projectNames=None, includeLibs=False):
+def sorted_deps(projectNames=None, includeLibs=False, includeAnnotationProcessors=False):
     """
     Gets projects and libraries sorted such that dependencies
     are before the projects that depend on them. Unless 'includeLibs' is
@@ -742,7 +742,7 @@
         projects = [project(name) for name in projectNames]
 
     for p in projects:
-        p.all_deps(deps, includeLibs)
+        p.all_deps(deps, includeLibs=includeLibs, includeAnnotationProcessors=includeAnnotationProcessors)
     return deps
 
 class ArgParser(ArgumentParser):
@@ -1296,7 +1296,7 @@
     if args.only is not None:
         sortedProjects = [project(name) for name in args.only.split(',')]
     else:
-        sortedProjects = sorted_deps(projects)
+        sortedProjects = sorted_deps(projects, includeAnnotationProcessors=True)
     
     for p in sortedProjects:
         if p.native:
@@ -1380,6 +1380,8 @@
                             log('could not file .class directive in Jasmin source: ' + src)
                     else:
                         dst = join(outputDir, src[len(sourceDir) + 1:])
+                        if not exists(dirname(dst)):
+                            os.makedirs(dirname(dst))
                         if exists(dirname(dst)) and (not exists(dst) or os.path.getmtime(dst) != os.path.getmtime(src)):
                             shutil.copyfile(src, dst)
 
@@ -1410,18 +1412,12 @@
             javacArgs += ['-J-Xdebug', '-J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=' + str(java().debug_port)]
 
         if hasattr(p, 'annotationProcessors') and len(p.annotationProcessors) > 0:
-            annotationProcessors = []
-            for apProject in p.annotationProcessors:
-                apClasses = project(apProject).annotationProcessorClasses
-                if len(apClasses) == 0:
-                    abort("Project " + p + " specifies " + apProject + " as an annotation processor but " + apProject + " does not specifiy any annotation processor class")
-                annotationProcessors += apClasses
-            
+            processorPath = classpath(p.annotationProcessors, resolve=True)
             genDir = p.source_gen_dir();
             if exists(genDir):
                 shutil.rmtree(genDir)
             os.mkdir(genDir)
-            javacArgs += ['-processor', ",".join(annotationProcessors), "-s", genDir]
+            javacArgs += ['-processorpath', join(processorPath), '-s', genDir] 
         else:
             javacArgs += ['-proc:none']
 
@@ -1467,37 +1463,53 @@
         return args
     return None
 
-def processorjars(args):
-    hasProcessorJars = []
+def processorjars():
+    projects = set([])
     
     for p in sorted_deps():
-        if hasattr(p, 'annotationProcessorClasses') and len(p.annotationProcessorClasses) > 0:
-            hasProcessorJars.append(p)
+        if _needsEclipseJarBuild(p):
+            projects.add(p)
             
-    if len(hasProcessorJars) <= 0:
+    if len(projects) <= 0:
         return
     
-    build(['--projects', ",".join(map(lambda p: p.name, hasProcessorJars))])
-
-    for p in hasProcessorJars:
-        spDir = join(p.output_dir(), 'META-INF', 'services')
-        if not exists(spDir):
-            os.makedirs(spDir)
-        spFile = join(spDir, 'javax.annotation.processing.Processor')
-        with open(spFile, 'w') as fp:
-            fp.writelines(p.annotationProcessorClasses)
-        created = False
-        for dep in p.all_deps([], False):
-            if created:
-                cmd = 'uf'
-            else:
-                cmd = 'cf'
-                created = True
-            jarCmd = [java().jar, cmd, join(p.dir, p.name + 'AnnotationProcessor.jar'), '-C', dep.output_dir(), '.']
-            subprocess.check_call(jarCmd)
-            log('added ' + dep.name + ' to ' + p.name + '.jar');
+    build(['--projects', ",".join(map(lambda p: p.name, projects))])
+
+    for p in projects:
+        targetJar = join(p.dir, p.name + '.jar')
+        jar(targetJar, [p.output_dir()])
             
 
+def jar(destFileName, dirs):
+    lib = library("ANT_JAR_TOOL", fatalIfMissing=False)
+    
+    if lib is None :
+        log('No library ANT_JAR_TOOL defined. Falling back to JDK Jar tool.');
+        _java_jar_tool(destFileName, dirs)
+    else:
+        _ant_jar_tool(lib, destFileName, dirs)
+
+def _java_jar_tool(destFileName, dirs):
+    created = False
+    for directory in dirs:
+        if created:
+            cmd = 'uf'
+        else:
+            cmd = 'cf'
+            created = True
+        jarCmd = [java().jar, cmd, destFileName, '-C', directory, '.']
+        subprocess.check_call(jarCmd)
+    
+def _ant_jar_tool(lib, destFileName, dirs):
+    antJar = lib.get_path(True)
+    
+    jarCmd = [java().java, '-jar', antJar, destFileName]
+    for directory in dirs :
+        jarCmd.append(directory)
+        
+    subprocess.check_call(jarCmd)
+
+
 def canonicalizeprojects(args):
     """process all project files to canonicalize the dependencies
 
@@ -1866,7 +1878,7 @@
     if suite is None:
         suite = _mainSuite
         
-    processorjars([])
+    processorjars()
 
     for p in projects():
         if p.native:
@@ -1884,6 +1896,13 @@
                 os.mkdir(srcDir)
             out.element('classpathentry', {'kind' : 'src', 'path' : src})
 
+        if hasattr(p, 'annotationProcessors') and len(p.annotationProcessors) > 0:
+            genDir = p.source_gen_dir();
+            if exists(genDir):
+                shutil.rmtree(genDir)
+            os.mkdir(genDir)
+            out.element('classpathentry', {'kind' : 'src', 'path' : 'src_gen'})
+
         # Every Java program depends on the JRE
         out.element('classpathentry', {'kind' : 'con', 'path' : 'org.eclipse.jdt.launching.JRE_CONTAINER'})
 
@@ -1975,6 +1994,23 @@
                 out.element('name', data=buildCommand)
                 out.element('arguments', data='')
                 out.close('buildCommand')
+
+        if (_needsEclipseJarBuild(p)):
+            out.open('buildCommand')
+            out.element('name', data='org.eclipse.ui.externaltools.ExternalToolBuilder')
+            out.element('triggers', data='auto,full,incremental,')
+            out.open('arguments')
+            out.open('dictionary')
+            out.element('key', data = 'LaunchConfigHandle')
+            out.element('value', data = _genEclipseJarBuild(p))
+            out.close('dictionary')
+            out.open('dictionary')
+            out.element('key', data = 'incclean')
+            out.element('value', data = 'true')
+            out.close('dictionary')
+            out.close('arguments')
+            out.close('buildCommand')       
+                
         out.close('buildSpec')
         out.open('natures')
         out.element('nature', data='org.eclipse.jdt.core.javanature')
@@ -2010,7 +2046,7 @@
             out.element('factorypathentry', {'kind' : 'PLUGIN', 'id' : 'org.eclipse.jst.ws.annotations.core', 'enabled' : 'true', 'runInBatchMode' : 'false'})
             for ap in p.annotationProcessors:
                 apProject = project(ap)
-                out.element('factorypathentry', {'kind' : 'WKSPJAR', 'id' : '/' + apProject.name + '/' + apProject.name + 'AnnotationProcessor.jar', 'enabled' : 'true', 'runInBatchMode' : 'false'})
+                out.element('factorypathentry', {'kind' : 'WKSPJAR', 'id' : '/' + apProject.name + '/' + apProject.name + '.jar', 'enabled' : 'true', 'runInBatchMode' : 'false'})
                 for dep in apProject.all_deps([], True):
                     if dep.isLibrary():
                         if not hasattr(dep, 'eclipse.container') and not hasattr(dep, 'eclipse.project'):
@@ -2022,11 +2058,79 @@
                                     # safest to simply use absolute paths.
                                     path = join(suite.dir, path)
                                 out.element('factorypathentry', {'kind' : 'EXTJAR', 'id' : path, 'enabled' : 'true', 'runInBatchMode' : 'false'})
+                    else:
+                        out.element('factorypathentry', {'kind' : 'WKSPJAR', 'id' : '/' + dep.name + '/' + dep.name + '.jar', 'enabled' : 'true', 'runInBatchMode' : 'false'})
             out.close('factorypath')
             update_file(join(p.dir, '.factorypath'), out.xml(indent='\t', newl='\n'))
 
     make_eclipse_attach('localhost', '8000', deps=projects())
 
+
+def _needsEclipseJarBuild(p):
+    processors = set([])
+    
+    for otherProject in projects():
+        if hasattr(otherProject, 'annotationProcessors') and len(otherProject.annotationProcessors) > 0:
+            for processorName in otherProject.annotationProcessors:
+                processors.add(project(processorName, fatalIfMissing=True))
+                 
+    if p in processors:
+        return True
+    
+    for otherProject in processors:
+        deps = otherProject.all_deps([], True)
+        if p in deps:
+            return True
+    
+    return False
+
+def _genEclipseJarBuild(p):
+    externalToolDir = '.externalToolBuilders'
+    relPath = join(externalToolDir, 'Jar.launch') 
+    absPath = join(p.dir, relPath)
+    
+    if not exists(join(p.dir, externalToolDir)):
+        os.makedirs(join(p.dir, externalToolDir))
+    
+    antOut = XMLDoc()
+    antOut.open('project', {'name' : p.name, 'default' : 'default', 'basedir' : '.'})
+    antOut.open('target', {'name' : 'default'})
+    antOut.open('jar', {'destfile' : p.name + '.jar'})
+    antOut.element('fileset', {'dir' : p.output_dir()})
+    antOut.close('jar')
+    antOut.close('target')
+    antOut.close('project')
+    
+    update_file(join(p.dir, 'eclipse-build.xml'), antOut.xml(indent='\t', newl='\n'))
+    
+    launchOut = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
+<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
+<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
+<booleanAttribute key="org.eclipse.ant.uiSET_INPUTHANDLER" value="false"/>
+<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;resources&gt;&#13;&#10;&lt;item path=&quot;/""" + p.name + """&quot; type=&quot;4&quot;/&gt;&#13;&#10;&lt;/resources&gt;}"/>
+<booleanAttribute key="org.eclipse.debug.core.capture_output" value="false"/>
+<booleanAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_OUTPUT_ON" value="false"/>
+<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="true"/>
+<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.ant.internal.launching.remote.InternalAntRunner"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value=""" + '"' + p.name + '"' + """/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/""" + p.name + """/eclipse-build.xml}"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="auto,full,incremental"/>
+<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/""" + p.name + """}"/>
+<stringAttribute key="process_factory_id" value="org.eclipse.ant.ui.remoteAntProcessFactory"/>
+</launchConfiguration>
+"""
+    update_file(absPath, launchOut)
+    
+    return "<project>/.externalToolBuilders/Jar.launch"
+
+
+
+
 def netbeansinit(args, suite=None):
     """(re)generate NetBeans project configurations"""
 
@@ -2063,9 +2167,10 @@
         out.element('explicit-platform', {'explicit-source-supported' : 'true'})
         out.open('source-roots')
         out.element('root', {'id' : 'src.dir'})
+        if hasattr(p, 'annotationProcessors') and len(p.annotationProcessors) > 0:
+            out.element('root', {'id' : 'src.ap-source-output.dir'})
         out.close('source-roots')
         out.open('test-roots')
-        out.element('root', {'id' : 'test.src.dir'})
         out.close('test-roots')
         out.close('data')
 
@@ -2099,12 +2204,18 @@
         out = StringIO.StringIO()
         jdkPlatform = 'JDK_' + java().version
 
+        annotationProcessorEnabled = "false"
+        annotationProcessorReferences = ""
+        annotationProcessorSrcFolder = ""
+        if hasattr(p, 'annotationProcessors') and len(p.annotationProcessors) > 0:
+            annotationProcessorEnabled = "true"
+            annotationProcessorSrcFolder = "src.ap-source-output.dir=${build.generated.sources.dir}/ap-source-output"
+
         content = """
-annotation.processing.enabled=false
-annotation.processing.enabled.in.editor=false
+annotation.processing.enabled=""" + annotationProcessorEnabled + """
+annotation.processing.enabled.in.editor=""" + annotationProcessorEnabled + """
 annotation.processing.processors.list=
 annotation.processing.run.all.processors=true
-annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
 application.title=""" + p.name + """
 application.vendor=mx
 build.classes.dir=${build.dir}
@@ -2134,15 +2245,11 @@
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.deprecation=false
-javac.processorpath=\\
-    ${javac.classpath}
 javac.source=1.7
 javac.target=1.7
 javac.test.classpath=\\
     ${javac.classpath}:\\
     ${build.classes.dir}
-javac.test.processorpath=\\
-    ${javac.test.classpath}
 javadoc.additionalparam=
 javadoc.author=false
 javadoc.encoding=${source.encoding}
@@ -2170,7 +2277,8 @@
 run.test.classpath=\\
     ${javac.test.classpath}:\\
     ${build.test.classes.dir}
-test.src.dir=
+test.src.dir=./test
+""" + annotationProcessorSrcFolder + """
 source.encoding=UTF-8""".replace(':', os.pathsep).replace('/', os.sep)
         print >> out, content
 
@@ -2188,7 +2296,19 @@
                 print >> out, 'src.' + src + '.dir=${' + ref + '}'
 
         javacClasspath = []
-        for dep in p.all_deps([], True):
+        
+        deps = p.all_deps([], True)
+        annotationProcessorOnlyDeps = []
+        if hasattr(p, 'annotationProcessors') and len(p.annotationProcessors) > 0:
+            for ap in p.annotationProcessors:
+                apProject = project(ap)
+                if not apProject in deps:
+                    deps.append(apProject)
+                    annotationProcessorOnlyDeps.append(apProject)
+        
+        annotationProcessorReferences = [];
+        
+        for dep in deps:
             if dep == p:
                 continue;
 
@@ -2208,11 +2328,16 @@
                 print >> out, 'project.' + n + '=' + relDepPath
                 print >> out, ref + '=${project.' + n + '}/dist/' + dep.name + '.jar'
 
-            javacClasspath.append('${' + ref + '}')
+            if not dep in annotationProcessorOnlyDeps:
+                javacClasspath.append('${' + ref + '}')
+            else:
+                annotationProcessorReferences.append('${' + ref + '}')
+                annotationProcessorReferences +=  ":\\\n    ${" + ref + "}"
 
         print >> out, 'javac.classpath=\\\n    ' + (os.pathsep + '\\\n    ').join(javacClasspath)
-
-
+        print >> out, 'javac.test.processorpath=${javac.test.classpath}\\\n    ' + (os.pathsep + '\\\n    ').join(annotationProcessorReferences)
+        print >> out, 'javac.processorpath=${javac.classpath}\\\n    ' + (os.pathsep + '\\\n    ').join(annotationProcessorReferences)
+    
         updated = update_file(join(p.dir, 'nbproject', 'project.properties'), out.getvalue()) or updated
         out.close()
 
@@ -2223,7 +2348,6 @@
 
 def ideclean(args, suite=None):
     """remove all Eclipse and NetBeans project configurations"""
-
     def rm(path):
         if exists(path):
             os.remove(path)
@@ -2233,10 +2357,17 @@
             continue
 
         shutil.rmtree(join(p.dir, '.settings'), ignore_errors=True)
+        shutil.rmtree(join(p.dir, '.externalToolBuilders'), ignore_errors=True)
         shutil.rmtree(join(p.dir, 'nbproject'), ignore_errors=True)
         rm(join(p.dir, '.classpath'))
         rm(join(p.dir, '.project'))
         rm(join(p.dir, 'build.xml'))
+        rm(join(p.dir, 'eclipse-build.xml'))
+        try:
+            rm(join(p.dir, p.name + '.jar'))
+        except:
+            log("Error removing {0}".format(p.name + '.jar'))
+            
 
 def ideinit(args, suite=None):
     """(re)generate Eclipse and NetBeans project configurations"""