Мульти-входные компиляторы с несколькими выходами с Shake

Я экспериментирую с использованием Shake для создания кода Java, и я немного застреваю из-за необычной природы компилятора javac. В общем случае для каждого модуля большого проекта компилятор вызывается со всеми исходными файлами для этого модуля в качестве входных данных и выдает все выходные файлы за один проход. Впоследствии мы обычно берем файлы .class, созданные компилятором, и собираем их в JAR (в основном просто ZIP).

Например, типичный проект модуля Java устроен следующим образом:

  • a src каталог, содержащий несколько файлов .java, некоторые из которых содержат множество уровней в глубине дерева.
  • a bin, содержащий вывод компилятора. Обычно этот вывод следует за той же структурой каталогов и именами файлов, при этом .class заменяется на каждый .java файл, но сопоставление не обязательно один-к-одному: один .java файл может давать нуль много .class файлов!

Правила, которые я хотел бы определить в Shake, заключаются в следующем:

1) Если любой файл под src более новый, чем любой файл в bin, то удалите все содержимое bin и заново создайте с помощью:

javac -d bin <recursive list of .java files under src>

Я знаю, что это правило кажется чрезмерным, но без вызова компилятора мы не можем знать объем изменений в выходе, вызванный даже небольшим изменением в одном входном файле.

2), если любой файл в bin новее, чем module.jar, затем заново создайте module.jar с помощью:

jar cf module.jar -C bin .

Большое спасибо!

PS Ответы в вене "просто используйте Ant/Maven/Gradle/" не оценили! Я знаю, что эти инструменты предлагают сборку Java из коробки, но их сложнее компоновать и собирать. Вот почему я хочу поэкспериментировать с инструментом Haskell/Shake.

Ответ 1

Правила записи, которые производят несколько выходов, имена которых не могут быть статически определены, могут быть немного сложными. Обычный подход заключается в том, чтобы найти выход, имя которого статически известно и всегда need, или если оно не существует, создать поддельный файл для использования в качестве статического вывода (согласно ghc-make, файл .result). В вашем случае у вас module.jar как конечный вывод, поэтому я бы написал:

"module.jar" *> \out -> do
    javas <- getDirectoryFiles "" ["src//*.java"]
    need javas
    liftIO $ removeFiles "" ["bin//*"]
    liftIO $ createDirectory "bin"
    () <- cmd "javac -d bin" javas
    classes <- getDirectoryFiles "" ["bin//*.class"]
    need classes
    cmd "jar cf" [out] "-C bin ."

Нет никакого преимущества, чтобы разделить его на два правила, поскольку вы никогда не зависите от файлов .class (и не можете, так как они непредсказуемы по имени), и если какой-либо исходный файл изменяется, вы всегда будете rebuild module.jar в любом случае. Это правило содержит все зависимости, которые вы указываете, плюс, если вы добавляете/переименовываете/удаляете любой файл .java или .class, тогда он будет автоматически перекомпилироваться, поскольку отслеживается вызов getDirectoryFiles.