Главная идея статьи - это показать как заставить ЭТО (kotlin & maven toolchain) работать вместе. Детального описания Maven toolchain здесь не будет, не хочу заниматься банальным переводом руководств.

Начну с прелюдии. Как котлинисту, мне новые версии java как-то по боку, но тут в JDK 22 подъехала годнота - panama/foreign вышла из инкубатора. Для тех кто не в теме, эта фича дает вам возможность вызывать нативный код из 'динамической библиотеки' dll/so (прямо из java кода). Теперь вы можете вызывать системные функции сами, а не подключать неизвестные вам библиотеки.

Вкратце о Maven toolchain.

Эта фича позволяет подключать нужную версию jdk (или других инструментов) автоматически. До апреля 2024 года maven toolchain плагин был довольно слабенький (по сравнению с gradle toolchains) - он позволял выбирать toolchain/jdk только из

Но вот недавно (в апреле 2024) подъехала новая версия, которая поддерживает

  • $home/.m2/toolchains.xml файл

  • может подхватывать текущий JDK ($JAVA_HOME), если он удовлетворяет заданным критериям

  • делает поиск в стандартных директориях (например, C:/Program Files/...)

    • Реально прикольная штука, ничего вообще делать не нужно. Проверил на Windows - реально работает. Какое стандартное расположение жабы в Linux - не имею понятия, но можно хотя бы ввести разные JAVA_XXX_HOME (это уже неплохо).

  • делает поиск в переменных окружения по паттерну (например: JAVA11_HOME, JAVA22_HOME). Паттерн конфигурируем.

    • Между прочим, gradle toolchains тоже поддерживают этот подход, но насколько я знаю, переменные нужно добавлять файл проекта вручную.

  • custom toolchains

Сейчас maven toolchain даже немного лучше своего собрата из gradle. В gradle есть неприятный баг по игнорированию vendor (и др атрибутов) из $home/.m2/toolchains.xml, в результате невозможно отличить Oracle (standard) JDK от Oracle Graal JDK.

Перейдем к главному.

У нас всё ещё есть одна проблемка - maven kotlin plugin не дружит с maven toolchain plugin. По крайней мере я не нашел как ему сказать, чтобы он подружился.

Но... у maven kotlin plugin есть конфигурационный параметр jdkHome, который мапится на maven property "toolchain.jdk.version". Это и будет нашим спасением - нужно взять JDK home, найденный toolchain plugin и установить его в соответсвующее свойство. Как по мне решение +- надежное (весь рискованный код помещен в try/catch), но это уже ваш выбор использовать ли его в production, или только в домашнем проекте. В худшем случае, оно просто не будет работать и вы просто вернетесь к старой доброй установке JAVA_HOME.

<project>
    ...
    <properties>
        <!-- Required JDK version in maven-artifact format. -->
        <toolchain.jdk.version>[22,)</toolchain.jdk.version>

        <!-- Required java-version in numeric format. -->
        <java.version>22</java.version>
        <java.release>${java.version}</java.release>
        <kotlin.version>1.7.0</kotlin.version>
        <kotlin.compiler.languageVersion>1.7</kotlin.compiler.languageVersion>

        <kotlin.compiler.jvmTarget>15</kotlin.compiler.jvmTarget>
        <kotlin.compiler.incremental>true</kotlin.compiler.incremental>

        <kotlin-maven-plugin.version>${kotlin.version}</kotlin-maven-plugin.version>
    </properties>

    <build>
        <plugins>
            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-toolchains-plugin</artifactId>
              <version>3.2.0</version>
              <executions>
                <execution>
                  <goals>
                    <goal>select-jdk-toolchain</goal>
                  </goals>
                </execution>
              </executions>
              <configuration>
                <version>${toolchain.jdk.version}</version>
                <!-- Optional old config. Let's keep it to avoid IDEA warning. -->
                <toolchains>
                  <jdk>
                    <version>${toolchain.jdk.version}</version>
                  </jdk>
                </toolchains>
              </configuration>
            </plugin>
            
            <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>build-helper-maven-plugin</artifactId>
              <version>3.4.0</version>
              <executions>
                <execution>
                  <id>use-maven-toolchain-jdk-for-kotlin</id>
                  <goals>
                    <goal>bsh-property</goal>
                  </goals>
                  <configuration>
                    <source><![CDATA[
                      import org.apache.maven.plugin.descriptor.PluginDescriptor;
            
                      String toolChainJdk = null;
            
                      try {
                        PluginDescriptor pd = new PluginDescriptor();
                        pd.setGroupId("org.apache.maven.plugins");
                        pd.setArtifactId("maven-toolchains-plugin");
            
                        Map pluginContext = session.getPluginContext(pd, project);
                        if (pluginContext == null)
                          throw new IllegalStateException("maven-toolchains-plugin plugin context is not found. Probably it is not set up.");
            
                        // A bit risky part of code: I'm not 100% sure that this class will be used in the future.
                        // It is not specified in the org.apache.maven.model.ConfigurationContainer
                        // (there is just java.lang.Object, but it must be some DOM)
            
                        Object toolchainPluginContext = pluginContext.get("toolchain-jdk");
                        if (toolchainPluginContext != null) {
                          // We can use type Object in bean-shell script instead of Xpp3Dom.
                          // Real type is XML DOM object (currently type is org.codehaus.plexus.util.xml.Xpp3Dom class)
            
                          Object config = toolchainPluginContext.getConfiguration();
                          if (config != null && config.getChildCount() > 0)
                            toolChainJdk = config.getChild(0).getValue().trim();
                        }
                      } catch (Exception ex) {
                        log.error("toolchain-jdk is not found. " + ex.getMessage(), ex);
            
                        // Or we can rethrow error just there.
                        // throw new IllegalStateException("toolchain-jdk is not found.", ex);
                      }
            
                      String requiredJdkVersion = project.getProperties().getProperty("toolchain.jdk.version");
                      if (toolChainJdk != null && !toolChainJdk.isEmpty()) {
                        project.getProperties().setProperty("kotlin.compiler.jdkHome", toolChainJdk);
            
                        log.info("toolchain-jdk for version '" + requiredJdkVersion + "' is " + toolChainJdk);
                        log.info("It will be used for kotlin compiler");
                      }
                      else {
                        String currentJavaHome = System.getProperty("java.home");
            
                        log.info("toolchain-jdk for version '" + requiredJdkVersion + "' is not found.");
                        log.info("  Possible reasons");
                        log.info("    * default java_home matches required java version ");
                        log.info("    * maven-toolchains-plugin is not configured properly");
                        log.info("  ");
                        log.info("Default " + currentJavaHome + " will be used.");
                      }
            
                      ]]>
                    </source>
                  </configuration>
                </execution>
              </executions>
            </plugin>
        
        </plugins>
    </build>
</project>

Полные исходники на github

Полезные ссылки

Комментарии (1)


  1. aleksandy
    02.05.2024 12:48
    +1

    Как костыль, конечно, годно, но лучше, конечно, все эти приседания отправить PR-ом в соответствующий плагин, чтобы это работало из коробки, а не вот эта вот мешанина из xml-я и java-кода.

    Не надо превращать pom-ник в ant/gradle-скрипт.