openjdk.ruOpenJDK на русском

JEP 201: Modular Source Code

Модульный исходный код

АвторMark Reinhold
ОтветственныйAlan Bateman
ТипFeature
ОбластьImplementation
СтатусClosed / Delivered
Выпуск9
Обсуждениеjigsaw dash dev at openjdk dot java dot net
ТрудоёмкостьL
ДлительностьL
БлокируетJEP 200: The Modular JDK
Связан сJEP 220: Modular Run-Time Images
РецензентыAlan Bateman, Alex Buckley, Mandy Chung, Paul Sandoz
ОдобренBrian Goetz
Создан2014/07/22 14:08
Обновлён2020/12/07 14:29
Задача8051619

Аннотация

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

Что не является целью

Этот JEP не меняет структуру двоичных образов JRE и JDK и не вводит систему модулей. Эта работа описана в связанных JEP 220 и 261.

Этот JEP определяет новую структуру исходного кода JDK. Эту структуру можно использовать и за пределами JDK, но разработка общепринятой универсальной модульной структуры исходного кода не является целью этого JEP.

Мотивация

Проект Jigsaw ставит целью спроектировать и реализовать стандартную систему модулей для платформы Java SE и применить эту систему к самой платформе и к JDK. Его основные цели: упростить масштабирование реализаций платформы вплоть до небольших устройств, повысить безопасность и удобство сопровождения, сделать возможной более высокую производительность приложений и дать разработчикам более удобные инструменты для разработки крупных систем.

Причины реорганизовать исходный код:

  1. дать разработчикам JDK возможность познакомиться с модульной структурой системы;

  2. сохранять эту структуру и в дальнейшем, обеспечивая соблюдение границ модулей при сборке, ещё до появления системы модулей; и

  3. позволить разработке проекта Jigsaw продвигаться без необходимости каждый раз «перетасовывать» нынешний немодульный исходный код в модульную форму.

Описание

Текущая схема

Сегодня большая часть исходного кода JDK организована примерно по схеме, которая восходит к 1997 году. В сокращённом виде:

src/{share,$OS}/{classes,native}/$PACKAGE/*.{java,c,h,cpp,hpp}

где:

  • каталог share содержит общий кроссплатформенный код;

  • каталог $OS содержит код, специфичный для операционной системы, где $OS — одно из значений solaris, windows и т. д.;

  • каталог classes содержит исходные файлы Java и, возможно, файлы ресурсов;

  • каталог native содержит исходные файлы на C или C++; и

  • $PACKAGE — имя соответствующего пакета Java API, в котором точки заменены косыми чертами.

Простой пример: исходный код класса java.lang.Object в репозитории jdk находится в двух файлах, один на Java, другой на C:

src/share/classes/java/lang/Object.java
          native/java/lang/Object.c

Пример посложнее: исходный код package-private классов java.lang.ProcessImpl и ProcessEnvironment зависит от операционной системы; для Unix-подобных систем он находится в трёх файлах:

src/solaris/classes/java/lang/ProcessImpl.java
                              ProcessEnvironment.java
            native/java/lang/ProcessEnvironment_md.c

(Да, каталог второго уровня называется solaris, хотя этот код относится ко всем производным Unix; подробнее об этом ниже.)

В src/{share,$OS} есть несколько каталогов, которые не соответствуют текущей структуре, в том числе:

Directory                     Content
--------------------------    --------------------------
src/{share,$OS}/back          JDWP back end
                bin           Java launcher
                instrument    Instrumentation support
                javavm        Exported JVM include files
                lib           Files for $JAVA_HOME/lib
                transport     JDWP transports

Новая схема

Модуляризация JDK даёт редкую возможность полностью перестроить исходный код, чтобы его было проще сопровождать. Мы реализуем следующую схему во всех репозиториях леса JDK, кроме hotspot. В сокращённом виде:

src/$MODULE/{share,$OS}/classes/$PACKAGE/*.java
                        native/include/*.{h,hpp}
                               $LIBRARY/*.{c,cpp}
                        conf/*
                        legal/*

где:

  • $MODULE — имя модуля (например, java.base);

  • каталог share, как и раньше, содержит общий кроссплатформенный код;

  • каталог $OS, как и раньше, содержит код, специфичный для операционной системы, где $OS — одно из значений unix, windows и т. д.;

  • каталог classes, как и раньше, содержит исходные файлы Java и файлы ресурсов, организованные в дерево каталогов, повторяющее иерархию $PACKAGE их API;

  • каталог native, как и раньше, содержит исходные файлы на C или C++, но организованы они иначе:

    • каталог include содержит заголовочные файлы C или C++, предназначенные для экспорта и внешнего использования (например, jni.h);

    • исходные файлы на C или C++ помещаются в каталог $LIBRARY, имя которого совпадает с именем разделяемой библиотеки или DLL, с которой будет скомпонован скомпилированный код (например, libjava или libawt); и, наконец,

  • каталог conf содержит конфигурационные файлы, которые предназначены для редактирования конечными пользователями (например, net.properties).

  • Каталог legal содержит юридические уведомления.

Перепишем предыдущие примеры: исходный код класса java.lang.Object размещается так:

src/java.base/share/classes/java/lang/Object.java
                    native/libjava/Object.c

Исходный код package-private классов java.lang.ProcessImpl и ProcessEnvironment размещается так:

src/java.base/unix/classes/java/lang/ProcessImpl.java
                                     ProcessEnvironment.java
                   native/libjava/ProcessEnvironment_md.c

(Здесь мы наконец воспользовались возможностью переименовать каталог solaris в unix.)

Содержимое каталогов, которые сейчас находятся в src/{share,$OS} и не соответствуют текущей структуре, теперь находится в соответствующих модулях:

Directory                     Module
--------------------------    --------------------------
src/{share,$OS}/back          jdk.jdwp.agent
                bin           java.base
                instrument    java.instrument
                javavm        java.base
                lib           $MODULE/{share,$OS}/conf
                transport     jdk.jdwp.agent

Файлы в текущем каталоге lib, которые не предназначены для редактирования конечными пользователями, теперь стали файлами ресурсов.

Изменения в системе сборки

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

Побочное преимущество компиляции модулей вместо репозиториев в том, что код в репозиториях corba, jaxp и jaxws может использовать новые возможности языка Java и новые API. Раньше это было запрещено, поскольку эти репозитории компилировались раньше репозитория jdk.

Скомпилированные классы в промежуточной сборке (т. е. не в сборке образа) разделены по модулям. Там, где сегодня у нас есть:

jdk/classes/*.class

переработанная система сборки создаёт:

jdk/modules/$MODULE/*.class

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

Система сборки по мере возможности обеспечивает соблюдение границ модулей во время сборки. Если граница модуля нарушена, сборка завершится с ошибкой.

Альтернативы

Возможно множество других схем размещения исходного кода, в том числе:

  1. Оставить {share,$OS} на верхнем уровне, с каталогом modules для файлов классов модуля:

    src/{share,$OS}/modules/$MODULE/$PACKAGE/*.java
                    native/include/*.{h,hpp}
                           $LIBRARY/*.{c,cpp}
                    conf/*
  2. Поместить всё в соответствующий каталог $MODULE, но оставить {share,$OS} на верхнем уровне:

    src/{share,$OS}/$MODULE/classes/$PACKAGE/*.java
                            native/include/*.{h,hpp}
                                   $LIBRARY/*.{c,cpp}
                            conf/*
  3. Опустить {share,$OS} в каталоги $MODULE, как в настоящем предложении, но убрать промежуточный каталог classes и добавить к именам каталогов native и conf префикс в виде подчёркивания — всё это ради упрощения распространённого случая модулей только на Java:

    src/$MODULE/{share,$OS}/$PACKAGE/*.java
                            _native/include/*.{h,hpp}
                                    $LIBRARY/*.{c,cpp}
                            _conf/*
  4. Вариант схемы 3, но с {share,$OS} на верхнем уровне:

    src/{share,$OS}/$MODULE/$PACKAGE/*.java
                            _native/include/*.{h,hpp}
                                    $LIBRARY/*.{c,cpp}
                            _conf/*
  5. Ещё один вариант схемы 3, в котором {share,$OS} опущен ещё глубже, чтобы дополнительно упростить случай модулей только на Java без кода, специфичного для $OS:

    src/$MODULE/$PACKAGE/*.java
                _native/include/*.{h,hpp}
                        $LIBRARY/*.{c,cpp}
                _conf/*
                _$OS/$PACKAGE/*.java
                    _native/include/*.{h,hpp}
                            $LIBRARY/*.{c,cpp}
                    _conf/*

Мы отвергли схемы с подчёркиваниями (3–5) как слишком непривычные и неудобные для навигации. Мы предпочитаем настоящее предложение схемам 1 и 2, потому что оно требует наименьших изменений по сравнению с текущей схемой и при этом размещает весь исходный код модуля в одном каталоге. Инструменты и скрипты, зависящие от текущей схемы, придётся переработать, но по крайней мере для исходного кода на Java структура внутри каждого каталога $MODULE остаётся прежней.

Другие вопросы, которые мы рассматривали:

  • Стоит ли выделить отдельные каталоги для файлов ресурсов, чтобы они хранились отдельно от исходных файлов Java? — Нет; похоже, это не стоит затраченных усилий.

  • Содержимое некоторых модулей распределено по нескольким репозиториям; это проблема? — Это неудобно, но система сборки справляется с этим благодаря магии механизма VPATH. Со временем мы можем перестроить репозитории, чтобы сократить число модулей, охватывающих несколько репозиториев, или даже избавиться от них совсем, но это выходит за рамки этого JEP.

  • У некоторых модулей несколько нативных библиотек; стоит ли объединить их, чтобы у каждого модуля было не больше одной нативной библиотеки? — Нет; в некоторых случаях нам нужна гибкость нескольких нативных библиотек на модуль, например, для «headless» и «headful» AWT.

Тестирование

Как уже сказано, этот JEP не меняет структуру двоичных образов JRE и JDK и вносит лишь незначительные изменения в их содержимое. Поэтому мы проверили это изменение, сравнив образы, собранные с ним, с образами, собранными без него, и запустив тесты для проверки фактических незначительных изменений.

Риски и допущения

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

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

Зависимости

Этот JEP — второй из нескольких JEP проекта Jigsaw. Он включает определение модульной структуры JDK из JEP 200, но явно от этого JEP не зависит.