Мульти-входные компиляторы с несколькими выходами с 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
.