指定动态编译的输出路径

12

我的Java 6动态编译正常工作,但我想更改输出路径。我已经尝试了很多方法(我就不多说了),但都没有成功。无论如何,这是有效的代码。

String[] filesToCompile = { "testFiles/Something.java" };
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(filesToCompile);
CompilationTask task = compiler.getTask(null, fileManager, null,null, null, compilationUnits);
System.out.println("Good? " + task.call());

但是输出结果会到源目录,这不是我想要的。

我怀疑答案可能在compiler.getTask中,但是API对于某些参数的含义并不是很明确。或者可能与fileManager有关。我已经尝试过了。

fileManager.setLocation(StandardLocation.locationFor("testFiles2"), null);

但是,再次猜测可能不是一个好主意。

谢谢!

编辑:我也尝试使用选项,像这样(如果有更紧凑的方式,请原谅):

    final List<String> optionsList = new ArrayList<String>();
    optionsList.add("-d what");
    Iterable<String> options = new Iterable<String>() {         
        public Iterator<String> iterator() {
            return optionsList.iterator();
        }
    };

我尝试将选项传递给getTask,但是收到了“Invalid Flag.”的错误信息。


+1 让我意识到现在有动态编译这样的东西! - Carl Smotricz
以前就有,现在变成内置的了! - Dan Rosenstark
3个回答

14

今天我也遇到了这个问题。

解决方法是在FileManager中指定输出目录,使用常规的getTask方法而不是`run`。

fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(outputDir));

就是这样了!! :)

文档有点误导,我的意思是,一个示例可能非常方便。但最终它带我到了那里。

编辑

这里有一个运行示例:

    // write the test class
    File sourceFile   = new File("First.java");
    FileWriter writer = new FileWriter(sourceFile);

    writer.write(
            "package load.test;\n" +
            "public class First{}"
    );
    writer.close();

    // Get the java compiler for this platform
    JavaCompiler compiler    = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(
            null,
            null,
            null);

    //--           H E R E    --// 
    // Specify where to put the genereted .class files
    fileManager.setLocation(StandardLocation.CLASS_OUTPUT, 
                            Arrays.asList(new File("/tmp")));
    // Compile the file
    compiler
        .getTask(null,
                fileManager,
                null,
                null,
                null,
                fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)))
        .call();
    fileManager.close();

    // delete the file
    sourceFile.deleteOnExit();

我已经有一段时间没有看过这个问题了。所以你的意思是说,你的答案实际上解决了整个问题? - Dan Rosenstark
运行良好 - 与在getTask()选项参数中使用“-d”相同,但更简洁。 - Atorian

7

第一篇帖子中的代码可以正常工作,但是会抛出以下错误:

java.lang.IllegalArgumentException: invalid flag: -d folder

这是因为传递"-d folder"会使解析器认为它正在解析一个选项。选项必须像"-d","folder"一样分开。
以下是一个工作示例:
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = javaCompiler.getStandardFileManager(null, null, null); 

String[] options = new String[] { "-d", "output" };
File[] javaFiles = new File[] { new File("src/gima/apps/flip/TestClass.java") };

CompilationTask compilationTask = javaCompiler.getTask(null, null, null,
        Arrays.asList(options),
        null,
        sjfm.getJavaFileObjects(javaFiles)
);
compilationTask.call();

像"c:\foo bar\foo"或"c:/foo bar/foo"这样的路径怎么办?空格或斜杠能被正确解析吗? - Gobliins
我不确定,但我猜测现在参数已经被正确传递了(并且因为原始问题的本质恰好是参数没有被分开解析),所以不需要进行任何转义。你可以尝试并回报结果。 - Gima

4
我没有使用Java 6动态编译工具的经验,但是目前还没有其他人回答 :)
编译任务会得到一个“FileManager”对象。如果您使用标准的文件管理器,则类将在源目录树中生成。您可以提供自己的FileManager子类,并覆盖“getFileForOutput”方法。 getFileForOutput的API描述表明,这将影响您的输出(=类)文件的位置。
更新
如何连接文件管理器
ForwardingJavaFileManager、ForwardingFileObject和ForwardingJavaFileObject 无法对标准文件管理器的行为进行覆盖,因为它是通过调用编译器上的方法而不是调用构造函数创建的。因此,应该使用转发(或委托)。这些类使得大多数调用可以转发给给定的文件管理器或文件对象,同时允许自定义行为。例如,考虑如何记录所有对JavaFileManager.flush()的调用:
   final Logger logger = ...;
   Iterable<? extends JavaFileObject> compilationUnits = ...;
   JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
   StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, null, null);
   JavaFileManager fileManager = new ForwardingJavaFileManager(stdFileManager) {
       public void flush() {
           logger.entering(StandardJavaFileManager.class.getName(), "flush");
           super.flush();
           logger.exiting(StandardJavaFileManager.class.getName(), "flush");
       }
   };
   compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();

更新2

我研究了动态编译并建立了自己的应用程序来完成此操作。这段代码包含了一些繁琐的仪式(即它可以被简化),但它是有效的!

package yar;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class DynamicCompiler {

   JavaCompiler compiler;

   public DynamicCompiler() {
      this.compiler = ToolProvider.getSystemJavaCompiler();
      if (this.compiler == null) {
         throw new NullPointerException("Cannot provide system compiler.");
      }
   }

   public void compile() {
      this.compiler.run(null, System.out, System.err, 
            "-d", "testFiles2", 
            "testFiles/Hello1.java", "testFiles/Hello2.java");
   }

   /**
    * @param args
    */
   public static void main(String[] args) {
      try {
         DynamicCompiler dc = new DynamicCompiler();
         dc.compile();
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }

}

我不确定如何让这段代码与动态生成的Java文件列表一起工作;我可能会为每个源文件分别运行compiler.run


这可能是真的,但不幸的是getJavaFileObjects只在StandardJavaFileManager上。无论如何,我会看看能做什么。如果这是Ruby,你的答案就足够了,可以使用monkey patch并完成 :) - Dan Rosenstark
抱歉,我害怕你是正确的。对于这次徒劳无功的追寻感到抱歉! - Carl Smotricz
添加了一些关于如何将文件管理器组合起来的信息。这是直接从Compiler API中获取的,希望能解决问题。 - Carl Smotricz
感谢您在此问题上的坚持。我一直在使用getFileObjects而不是getJavaFileObjects,因此子类化确实有效,但由于我不知道如何创建一个位置对象(替换传递给getJavaFileObjects的对象),以便将类输出到新位置,因此仍然无法实现最终结果。 - Dan Rosenstark
太棒了。yar包。我感到受宠若惊。现在这个可以用,等我把它整合好了再回来感谢你。看起来args可以是一个字符串数组,所以应该没问题。 - Dan Rosenstark
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接