jenv и три места, где живёт версия Java (часть 2)
Три уровня приоритета — и грабля вторая
Разобравшись с export, я полез настраивать проекты. Здесь всё логично, но знать надо заранее.
Уровней три, приоритет такой: shell > local > global.
Команда — Область — Приоритет jenv global <версия> — вся система, умолчание — низкий jenv local <версия> — текущий каталог, пишет .java-version — средний jenv shell <версия> — текущая сессия терминала — высокий
И вот вторая грабля, на которой спотыкаются все: jenv local не меняет версию в уже открытой сессии. Ты стоишь в каталоге, выполнил jenv local 17.0.8, набираешь java -version — а там прежняя. Классическое «я же настроил, почему не работает». Для текущей сессии нужен jenv shell.
Грабля третья: он молчит
Самая неприятная из трёх.
Если в .java-version записана версия, которой нет в jenv versions, jenv её просто игнорирует. Не ругается. Не предупреждает и не подсказывает, что версию надо сначала добавить через jenv add. Ты уверен, что репозиторий сам всё настроил. Он не настроил ничего.
Мне такое поведение не нравится — лучше бы падал. Поэтому сверяй .java-version со списком jenv versions руками: сам jenv о расхождении не скажет.
Диагностика, если что-то не сходится: jenv doctor и jenv which java — покажет, какой бинарник реально дёргается.
Зачем .java-version в git
Это, пожалуй, лучшее, что jenv даёт команде. Версия перестаёт быть знанием в чьей-то голове и становится частью репозитория. Новый человек делает git clone и получает версию вместе с кодом.
Пара практических деталей:
— В файле может быть номер (17.0.8) или имя из jenv versions (openjdk-17.0.8). Второе стабильнее. — Оговорка честная: это конвенция, а не принуждение. Работает при условии «у коллеги стоит jenv и нужная JDK». У коллеги без jenv это просто текстовый файлик в корне, который ничего не делает.
Узел: три места, где живёт версия Java
Когда я закончил разбираться с JAVA_HOME, до меня дошло, что «версия Java на проекте» — это три разных настройки:
1. Что запускается, когда ты набираешь java в терминале. Скрипты, jshell, разовые прогоны, утилиты. 2. Чем собирается проект. JDK, на которой стартуют mvn и gradle, — а если в проекте настроены toolchains, то ещё и отдельная JDK, которой компилируется код. 3. Чем компилирует IDE. Project Structure → SDK.
Со вторым пунктом я сам сначала запутался, поэтому проговорю отдельно. Плагин export его частично закрывает: он выставляет JAVA_HOME, а от JAVA_HOME стартуют и Maven, и Gradle. Пока toolchains в проекте нет, эта же JDK код и компилирует — переключил версию через jenv, поменялась и сборка.
Связь рвётся в тот момент, когда toolchains появились. Дальше компилятор выбирается по правилам toolchain-резолвинга, и JAVA_HOME на него уже не влияет: за ним остаётся только вопрос, на какой JVM запустился сам билд-тул. В документации Gradle это разведено прямым текстом, к ней вернусь ниже. То есть export закрывает второй пункт ровно до первого проекта с toolchains, а дальше перестаёт.
Между тремя пунктами нет никакой связи. Никто не сверяет их между собой, ошибки не будет — всё просто работает по-разному в трёх местах. А наружу это вылезает самой дорогой фразой в командной разработке: «у меня собирается, у тебя нет».
jenv закрывает пункт первый и только его. Продавать его как решение «проблемы версий Java» — врать, поэтому и не получается написать про него простой гайд «поставь и пользуйся».