Maven: частичная компиляция перед генерацией кода

#java #maven

#java #maven

Вопрос:

tl; dr-edition: у меня есть компиляция, я знаю, что она завершится неудачей, но мне нужно подмножество классов, которые все еще можно скомпилировать в моей target/classes папке после компиляции. Я настроил <failOnError>false</failOnError> , но классы не генерируются, даже фиктивный класс, который не зависит от каких-либо других классов, кроме Object . Есть ли какая-то конфигурация для достижения этого?


У меня есть проект на базе maven, рабочий процесс которого состоит в основном из следующих (соответствующих) целей:

  • ...
  • init-compile

    Генератор кода (ниже) использует конфигурацию, основанную на отражении, поэтому в первом проходе я хочу попытаться скомпилировать как можно больше проекта, чтобы там не было ClassNotFoundExceptions . Эта компиляция настроена <failOnError>false</failOnError> так, чтобы сборка продолжалась.

    К сожалению (вы могли бы назвать это недостатком дизайна), конфигурация используется как для генерации кода (указание файла OWL и пространства имен для сопоставления пакетов), так и во время выполнения, поэтому она также содержит другие элементы, которые не нужны для генератора кода, но все еще читаются и, следовательно, необходимы в пути к классу, чтобыуспешно.

  • generate-model

    На этом этапе некоторые классы модели генерируются из OWL-онтологии, создавая код, который делает остальную часть проекта полностью компилируемой.

  • default-compile

    Теперь, очевидно, остальные классы должны быть скомпилированы

  • save-model

    Теперь экземпляры из онтологии считываются и сериализуются в файл для выполнения

  • ...

Примечание: как генерировать, так и сохранять использование модели maven-exec-plugin , но я искренне не думаю, что это вообще имеет значение.

Вопрос:

Когда я запускаю свою сборку mvn -e -U clean package source:jar javadoc:jar install:install , она завершается сбоем во время generate-model цели с ошибками, которых я пытаюсь избежать. target/classes пусто, поэтому кажется, что компилятор не выдает подмножество классов, которые он мог / должен был обрабатывать. Есть ли способ добиться этого?

У меня есть два обходных пути, которые мне оба не нравятся:

  • Редактирование конфигурационного файла «AST» перед его анализом в Java-объекты, чтобы анализировалась только часть, относящаяся к генератору кода (требуется настройка кода, к которому у меня есть доступ, но мой проект должен считаться неизменяемым);
  • и настроить init-compile цель так, чтобы она включала только необходимые классы (слишком негибко, потому что POM должен / может быть шаблоном для будущих приложений, использующих ту же модель).

Если вы можете представить себе другой способ решения моей проблемы, который вы можете увидеть из моего описания, я тоже был бы рад их услышать!

Комментарии:

1. Может быть, я что-то упускаю — ваша сборка не работает, когда вы перемещаете свою цель создания модели на этап генерации источников?

2. Конфигурация предоставляется в пути к классу, где она также находится во время выполнения приложения, забыл сказать, что, поэтому generate-model (и init-compile перед этим) выполняются во время ресурсов процесса. Но, как указано выше, реальная проблема более или менее заключается в том, что мне нужна начальная, неполная компиляция, которая предоставляет классы, на которые ссылаются в конфигурации. Думаю, я закончу с первым обходным путем, но мне все еще интересно, как этого добиться…

Ответ №1:

Во-первых, позвольте мне кратко изложить вашу проблему, чтобы убедиться, что я правильно ее понял.

  • У вас есть набор классов, функция которых в их скомпилированном виде заключается в настройке как генератора кода, так и среды выполнения. (Подмножество из них относится к генератору кода, но генерация завершится неудачно, если не будет представлена полная конфигурация. Таким образом, мы можем рассматривать это так, как если бы вся конфигурация была необходима.)

  • Затем у вас есть набор классов, которые будут сгенерированы как исходный код. У них есть время генерации, возможно, время компиляции и зависимость времени выполнения от классов конфигурации.

  • Наконец, у вас есть какой-то другой код, который зависит от времени компиляции от сгенерированных классов и зависит от времени выполнения как от сгенерированных классов, так и от классов конфигурации.

  • Однако ваши классы конфигурации не имеют никаких зависимостей во время компиляции ни от сгенерированных классов, ни от другого кода. Вы явно не говорите об этом, но я предполагаю это, иначе у вас проблема с циклической зависимостью.

Вот мое предложение: разделите проект на многомодульный («реакторный») проект. Ваш текущий проект будет модулем проекта reactor. Создайте новый модуль с именем «config» или аналогичный и переместите в него свои классы конфигурации. Пусть основной модуль зависит от него.

Если вам не нравятся многомодульные проекты, вы можете добиться того же, объявив дополнительное выполнение плагина компиляции, привязанного к фазе генерации исходных текстов. (Вы не говорите, но я предполагаю, что вы выполняете генерацию кода на этом этапе. Если вы объявите плагин компиляции перед плагином генератора кода в POM, Maven выполнит их в том же порядке.) Вы бы использовали фильтр «включить» плагина компиляции для компиляции только классов конфигурации. Для этого вам нужно будет иметь классы конфигурации в отдельном пакете от всех остальных, что в любом случае является хорошей практикой.

Комментарии:

1. Привет! Спасибо за ваш ответ! Разделение проекта теоретически является вариантом, но я думаю, что это немного излишне для моего использования (это тестирование производительности: у нас есть отдельные проекты для тестовых случаев, поэтому удвоение количества проектов является громоздким). Просто чтобы вы знали, я выбрал вариант обходного пути 1, где я прочитал всю конфигурацию, но проанализировал только ее поддерево как объекты Java 😉

Ответ №2:

Есть одно очень удобное решение — использовать Eclipse Java Compiler (EJC) вместо стандартного Oracle javac! Одним из преимуществ ECJ перед javac является то, что в нем допустимы ошибки, он пытается скомпилировать как можно больше и сохраняет уже сгенерированные файлы классов. EJC был разработан для использования в IDE, для высокоинтерактивной работы, где частичная компиляция является обязательной, но его также можно использовать как CLI или плагин Maven. Ребята из Plexus предоставляют EJC в качестве удобной зависимости Maven.

В Maven можно подключать компиляторы. У вас может быть несколько компиляций (выполнения компиляции), определенных в одном POM, и вы можете использовать разные компиляторы для каждого, предоставляя разработчику широкий выбор опций.

Пример кода POM:

 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <executions>
        <execution>
            <id>pre-compilation</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <compilerId>eclipse</compilerId>
                <!-- IMPORTANT. Select EJC as compiler instead of javac -->
                <failOnError>false</failOnError>
                <!-- IMPORTANT. When ECJ is used errors are reported 
                only as warnings, it continues in compilation, 
                try to compile as much as possible, keeps already 
                generated classes in target/classes -->
            </configuration>
        </execution>
        <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <!-- in the end recompile everything with standard javac.
                 This time no compilation errors are expected or tolerated. -->
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <useIncrementalCompilation>false</useIncrementalCompilation>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.plexus</groupId>
            <artifactId>plexus-compiler-eclipse</artifactId>
            <version>2.3</version>
        </dependency>
    </dependencies>
</plugin>

<!-- generate sources. This plugin executes in facet BETWEEN the compilations due to 
    'generate-sources' phase binding and relative position in POM -->
<plugin>
    <groupId>org.eclipse.xtext</groupId>
    <artifactId>xtext-maven-plugin</artifactId>
    <version>2.5.0</version>
    <executions>
        <execution>
            <id>generate-the-stuff</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  

Благодарности Габриэлю Акселю за его статью http://www.gabiaxel.com/2011/10/replacing-javac-with-eclipse-compiler.html

Этот подход может решить некоторые сложные циклические зависимости «от источника к сгенерированному источнику к источнику«, потенциально неразрешимые путем разделения на отдельные модули.

Кроме того, я хотел интегрировать генерацию исходного кода наиболее прозрачным способом. Необходимость перетасовки кода на основе зависимостей от сгенерированных источников окончательно нарушит его. Я хочу сгруппировать свой код на основе логического дизайна не из-за технических особенностей.

Если у вас генератор кода происходит с xtext, как в моем случае, и вы используете xtext-maven-plugin, скажем, недавно выпущенный 2.5.0, вам не нужно ничего настраивать, как в примере выше, плагин делает именно это под капотом.