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. Его основные цели: упростить масштабирование реализаций платформы вплоть до небольших устройств, повысить безопасность и удобство сопровождения, сделать возможной более высокую производительность приложений и дать разработчикам более удобные инструменты для разработки крупных систем.
Причины реорганизовать исходный код:
-
дать разработчикам JDK возможность познакомиться с модульной структурой системы;
-
сохранять эту структуру и в дальнейшем, обеспечивая соблюдение границ модулей при сборке, ещё до появления системы модулей; и
-
позволить разработке проекта 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
Структура сборок образов, как уже сказано, не меняется; в их содержимом есть очень незначительные различия.
Система сборки по мере возможности обеспечивает соблюдение границ модулей во время сборки. Если граница модуля нарушена, сборка завершится с ошибкой.
Альтернативы
Возможно множество других схем размещения исходного кода, в том числе:
-
Оставить
{share,$OS}на верхнем уровне, с каталогомmodulesдля файлов классов модуля:src/{share,$OS}/modules/$MODULE/$PACKAGE/*.java native/include/*.{h,hpp} $LIBRARY/*.{c,cpp} conf/* -
Поместить всё в соответствующий каталог
$MODULE, но оставить{share,$OS}на верхнем уровне:src/{share,$OS}/$MODULE/classes/$PACKAGE/*.java native/include/*.{h,hpp} $LIBRARY/*.{c,cpp} conf/* -
Опустить
{share,$OS}в каталоги$MODULE, как в настоящем предложении, но убрать промежуточный каталогclassesи добавить к именам каталоговnativeиconfпрефикс в виде подчёркивания — всё это ради упрощения распространённого случая модулей только на Java:src/$MODULE/{share,$OS}/$PACKAGE/*.java _native/include/*.{h,hpp} $LIBRARY/*.{c,cpp} _conf/* -
Вариант схемы 3, но с
{share,$OS}на верхнем уровне:src/{share,$OS}/$MODULE/$PACKAGE/*.java _native/include/*.{h,hpp} $LIBRARY/*.{c,cpp} _conf/* -
Ещё один вариант схемы 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 не зависит.