Здравствуйте, эта статья не про аниме, но мы точно знаем как пропатчить Idea для FreeBSD. И не боимся об этом рассказывать.

Intellij и FreeBSD
Помимо проблем с блокировками пользователей из РФ, у команды Intellij есть еще явное предубеждение против моей любимой операционной системы — FreeBSD, поддержку которой они постоянно ломают в своих продуктах, препятствуя использованию в этом окружении.
Но волю советского инженера не сломить, поэтому автор продолжает цинично использовать то что нельзя там где это невозможно, невзирая на мнение этой замечательной компании и ее сотрудников.
Потому что когда-то учил инженерное дело настоящим образом и неоднократно патчил Idea вручную. Ну да ладно, вернемся к теме:
на примере популярной среды разработки Intellij Idea, показываю как патчить софт на Java подручными средствами
с помощью синей изоленты.
Все поддается доработке если ты инженер.
Изучение проблемы
Недавно при попытке автоматического обновления, моя Idea в очередной раз самоубилась, не выдержав ужасов эксплуатации, поэтому был скачан последний релизный билд с официального сайта:
251.25410.129
К сожалению, в этой версии при попытке запуска появляется искусственная ошибка с сообщением про неподдерживаемую ОС, запуск Idea на этом останавливается:
./bin/idea.sh
[0.005s][warning][cds] Archived non-system classes are disabled because the java.system.class.loader property is specified (value = "com.intellij.util.lang.PathClassLoader"). To use archived non-system classes, this property must not be set
**Start Failed**
Internal error
java.lang.UnsupportedOperationException: Unsupported OS:FreeBSD
at com.intellij.openapi.application.PathManager.getLocalOS(PathManager.java:501)
at com.intellij.openapi.application.PathManager.platformPath(PathManager.java:927)
at com.intellij.openapi.application.PathManager.getDefaultConfigPathFor(PathManager.java:394)
at com.intellij.openapi.application.PathManager.getCustomOptionsDirectory(PathManager.java:448)
at com.intellij.openapi.application.PathManager.loadProperties(PathManager.java:710)
at com.intellij.idea.Main.mainImpl(Main.kt:65)
at com.intellij.idea.Main.main(Main.kt:47)
Эту же ошибку но в графическом исполнении вы можете узреть на заглавном скриншоте к статье — до такой степени в Jetbrains не любят BSD‑шников.
Исходный код сommunity-версии Idea находится в репозитории на Github, поэтому класс из которого выбрасывается ошибка:
com.intellij.openapi.application.PathManager
легко можно найти поиском по репозиторию.
Точное место выглядит так:
..
@ApiStatus.Internal
public static @NotNull OS getLocalOS() {
if (SystemInfoRt.isMac) {
return OS.MACOS;
}
else if (SystemInfoRt.isWindows) {
return OS.WINDOWS;
}
else if (SystemInfoRt.isLinux) {
return OS.LINUX;
}
else if (SystemInfoRt.isUnix) {
return OS.GENERIC_UNIX;
}
else {
throw new UnsupportedOperationException("Unsupported OS:" + SystemInfoRt.OS_NAME);
}
}
..
Как видите, основная логика находится в другом классе:
com.intellij.openapi.util.SystemInfoRt
который и отвечает за определение текущей ОС из переменных окружения.
Основная логика класса SystemInfoRt
выглядит следующим образом:
..
public static final String OS_NAME;
public static final String OS_VERSION;
static {
String name = System.getProperty("os.name");
String version = System.getProperty("os.version").toLowerCase(Locale.ENGLISH);
if (name.startsWith("Windows") && name.matches("Windows \\d+")) {
// for whatever reason, JRE reports "Windows 11" as a name and "10.0" as a version on Windows 11
try {
String version2 = name.substring("Windows".length() + 1) + ".0";
if (Float.parseFloat(version2) > Float.parseFloat(version)) {
version = version2;
}
}
catch (NumberFormatException ignored) { }
name = "Windows";
}
OS_NAME = name;
OS_VERSION = version;
}
private static final String _OS_NAME = OS_NAME.toLowerCase(Locale.ENGLISH);
public static final boolean isWindows = _OS_NAME.startsWith("windows");
public static final boolean isMac = _OS_NAME.startsWith("mac");
public static final boolean isLinux = _OS_NAME.startsWith("linux");
public static final boolean isFreeBSD = _OS_NAME.startsWith("freebsd");
public static final boolean isSolaris = _OS_NAME.startsWith("sunos");
public static final boolean isUnix = !isWindows;
public static final boolean isXWindow = isUnix && !isMac;
..
Казалось бы все ОК и проблемы не видно.
Для проверки я создал простенький shebang-скрипт (да Java теперь тоже так умеет), в который вставил логику из класса SystemInfoRt
:
#!/usr/local/openjdk24/bin/java --source 11
import java.util.Locale;
public class batchjob {
final static class SystemInfoRt {
public static final String OS_NAME;
public static final String OS_VERSION;
static {
String name = System.getProperty("os.name");
String version = System.getProperty("os.version")
.toLowerCase(Locale.ENGLISH);
if (name.startsWith("Windows") && name.matches("Windows \\d+")) {
// for whatever reason, JRE reports "Windows 11" as a name and
// "10.0" as a version on Windows 11
try {
String version2 = name.substring("Windows".length() + 1) + ".0";
if (Float.parseFloat(version2) > Float.parseFloat(version)) {
version = version2;
}
}
catch (NumberFormatException ignored) { }
name = "Windows";
}
OS_NAME = name;
OS_VERSION = version;
}
private static final String _OS_NAME = OS_NAME.toLowerCase(Locale.ENGLISH);
public static final boolean isWindows = _OS_NAME.startsWith("windows");
public static final boolean isMac = _OS_NAME.startsWith("mac");
public static final boolean isLinux = _OS_NAME.startsWith("linux");
public static final boolean isFreeBSD = _OS_NAME.startsWith("freebsd");
public static final boolean isSolaris = _OS_NAME.startsWith("sunos");
public static final boolean isUnix = !isWindows;
public static final boolean isXWindow = isUnix && !isMac;
public static final boolean isJBSystemMenu = isMac
&& Boolean.parseBoolean(System
.getProperty("jbScreenMenuBar.enabled", "true"));
public static final boolean isFileSystemCaseSensitive =
isUnix && !isMac || "true".equalsIgnoreCase(System
.getProperty("idea.case.sensitive.fs"));
private SystemInfoRt() {}
}
public static void main(String[] args) {
System.out.println("name: '" + System.getProperty("os.name")+"'");
System.out.println("unix:" + SystemInfoRt.isUnix);
}
}
Запуск показал что логика рабочая, название ОС и признак isUnix
определяются корректно:

Так как же тогда получилось что правильная логика не работает?
Все опросто:
версия класса в релизной версии отличается от версии в репозитории.
Чтобы в этом убедиться, достаточно декомпилировать PathManager.class
из релизной сборки Idea:

Как видите проверки на isUnix
тут нет:
..
else if (SystemInfoRt.isUnix) {
return OS.GENERIC_UNIX;
}
..
Что и порождает эту искусственную ошибку.
Исправление
Проблема найдена, что уже неплохо, но к сожалению чтобы ее исправить стандартным путем необходимо:
внести правку в код класса
PathManager
;пересобрать как минимум библиотеку, в которой этот класс находится, как максимум — среду целиком;
скопировать обновленную библиотеку в дистрибутив Idea, либо использовать собранную кастомную версию.
Помимо того что все правки, внесенные таким способом удалятся при следующем обновлении Idea, есть еще проблема самой сборки:
в Idea давно присутствуют нативные библиотеки, поэтому полная сборка из исходников под FreeBSD представляет определенную проблему.
Да, можно попробовать собрать Idea и под Linux, если у вас есть возможность выкачать ~20Гб
исходного кода и время для того чтобы развернуть весь тулчейн для сборки — вперед, это налучший вариант для долгосрочного сопровождения.
К сожалению в моем случае нужно было какое-то решение «здесь и сейчас», времени и сил для развертывания полноценной сборки всей Idea из исходников не было.
Так что мы пойдем другим путем ультрахардкора:
Java позволяет частичную пересборку с использованием готовой бинарной сборки с совпадающими классами.
Это означает что можно взять обновленную версию PathManager.java
из репозитория и собрать локально только один этот класс, подложив в CLASSPATH
библиотеки из бинарной сборки Idea.
Целиком скрипт сборки выглядит так:
#!/usr/local/bin/bash
# путь к распакованной Intellij Idea
export IDEA=/opt/app/idea-IC-251.25410.129/lib
# скачивание рабочей версии PathManager.java
curl https://raw.githubusercontent.com/JetBrains/intellij-community/refs/heads/master/platform/util/src/com/intellij/openapi/application/PathManager.java -o PathManager.java
# создаем каталоги пакетов
mkdir -p com/intellij/openapi/application
# компилируем класс с использованием библиотек из Idea
javac -cp .:$IDEA/annotations.jar:$IDEA/util.jar:$IDEA/util_rt.jar:$IDEA/util-8.jar com/intellij/openapi/application/PathManager.java
# создаем .jar-файл с пропатченной версией
jar cf patch.jar com/intellij/openapi/application/*.class
В результате выполнения в текущем каталоге должен появиться файл patch.jar
с исправленной версией PathManager
.
Но сборка патча лишь половина проблемы, вторая половина — как его теперь установить в текущую бинарную копию Idea не привлекая внимание санитаров.
Установка патча
В Java есть т. н. «иерархия загрузчиков классов» и учет порядка библиотек (JAR-файлов), указываемых в CLASSPATH
при запуске приложения.
Это означает, что если указать JAR
с патчем в списке CLASSPATH
до JAR
с оригиналом класса, то будет загружен и инициализирован класс из патча, а оригинал — пропущен.
В скрипте запуска Idea (файл bin/idea.sh
) есть перечисление стартовых библиотек:
..
CLASS_PATH="$IDE_HOME/lib/platform-loader.jar"
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/util-8.jar"
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/util.jar"
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/app-client.jar"
..
Так что для установки патча достаточно будет скопировать файл с патчем в каталог lib
и добавить его в этот список до оригинала util-8.jar
.
Выглядит это как-то так:
..
CLASS_PATH="$IDE_HOME/lib/platform-loader.jar"
# наш адский патч
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/patch.jar"
# библиотека с оригиналом класса, который будет пропущен при загрузке
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/util-8.jar"
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/util.jar"
CLASS_PATH="$CLASS_PATH:$IDE_HOME/lib/app-client.jar"
..
Ну и собственно результат:

Применимость
Это точно не последний случай, когда приходится вручную исправлять софт замечательной компании Jetbrains — альтернативные ОС там действительно не любят, поэтому описанная технология «кровавого патчинга» будет применяться во славу высоких технологий и прогресса еще не раз и не два.
Но мы BSD‑шники привычные — таков путь.
Добавлю, что возможность переопределения и частичной перекомпиляции чужих классов — очень мощный инструмент, который неоднократно меня выручал, поэтому вам точно стоит о нем знать.
Таким способом можно например вставлять отладочные строки внутрь классов Spring Framework, можно менять логику поведения классов из чужих библиотек и все это без заморочек с полной релизной сборкой, рефлексией, модификацией байт-кода или технологией Java Agent.
Этим же методом мы неоднократно делали бекпорт нужных фич, которые реализовывались только в новой версии библиотеки и никогда бы не появились в старой.
Собственно описываемый выше патч — пример такого бекпорта функционала, реализованного в текущей develop‑версии, но еще не перенесенного в релизную.
К сожалению известно о таком методе лишь очень небольшому количеству современных разработчиков, так что надеюсь этой статьей приоткрыл некоторым из читателей новые горизонты разработки на Java.
PS
Оригинал статьи как обычно в нашем блоге, опыт эксплуатации Idea в BSD‑системах — большой, поэтому смогу помочь и другим BSD‑шникам, если кому‑то из них вдруг придет в голову заниматься разработкой на BSD.
Контакты в профиле.
Комментарии (25)
Kahelman
26.05.2025 17:36Месье знает толк в извращениях :)
Yami-no-Ryuu
26.05.2025 17:36По моему, этот трюк все жава разработчики используют, обычно подкладывая подправленный класс из библиотеки в проект. Почему извращение?
softwind
26.05.2025 17:36Я пошёл другим путём: у меня idea запускается в docker, отображается на моих X-ах на макоси - делал проект под SCTP, ну и с дуру попробовал, а оно прижилось...
alex0x08 Автор
26.05.2025 17:36«А docker в яйце, а яйцо — в гробу, а гроб — на дереве. А деревьев там — тьма. И все в гробах.» Крепко смерть кощеевую запрятали однако!
tenteaday
26.05.2025 17:36почему бы не попробовать сначала запуститься с -Dos.name=Linux?
alex0x08 Автор
26.05.2025 17:36Тогда бы не было статьи ;)
Ну и упало бы в другом месте, поскольку при os.name=Linux пошла бы попытка загрузить нативные библиотеки - то что я чинил в прошлой серии.
tenteaday
26.05.2025 17:36если взять >=11 java то не упало бы, ну а статьи да- не было.
А вообще молодцы, уже починили.
https://youtrack.jetbrains.com/issue/IJPL-184145 перейдите на 2025.1 https://www.jetbrains.com/idea/download/other.htmlalex0x08 Автор
26.05.2025 17:36Ничего, отлетит в другом месте - официально BSD все также не поддерживается, поэтому ни тестовых сборок ни стендов с BSD у них нет, патчи принимают с неохотой и фактически в слепую.
tenteaday
26.05.2025 17:36это вопрос денег, и далеко не аренды тестовых серверов. Фря всегда была университетской, и денежного смысла совершенно нет.
Кроме того - какие плюсы у использования фри по сравнению с линуксом? Для разработчика могу сказать что отсутствие докера, альтернативный шедулер, другая сеть и немного не так работающие утилиты - постоянный источник гемора.
alex0x08 Автор
26.05.2025 17:36Кроме того - какие плюсы у использования фри по сравнению с линуксом?
Достаточно что нет и никогда не было чего-то такого или такого.
Но конечно Фря не про удобство, если вас концептуально не напрягает, что одни только исходники ядра Linux стали занимать больше места чем вся FreeBSD целиком — думаю стоит оставаться на линуксе.
DjUmnik
26.05.2025 17:36А как же девиз Java: Write once, run anywhere?
aleksandy
26.05.2025 17:36Так оно и запускается. Если не использовать платформозависимые подгружаемые библиотеки и не вставлять искусственные проверки, приводящие к исключениям.
spirit1984
26.05.2025 17:36Сразу видно, никогда не пытались использовать на практике Java). Если серьезно, то именно поэтому сделать хорошее десктопное приложение на Java, которое выглядело бы действительно красиво - крайне нетривиальная задача. Поскольку для этого требуется слишком уж тесно использовать особенно взаимодействия конкретной ОС с графической частью
X-P0rt3r
26.05.2025 17:36Решил по аналогии попробовать, вкорячивая последнюю версию Rider на Windows 7, которую уже не поддерживают все современные IDE. Но зависло уже на стадии окончания установки: "Starting post-installation steps in background process".
alex0x08 Автор
26.05.2025 17:36Если хотите "по аналогии", тогда одной попыткой установки не отделаетесь )
Разбирайтесь теперь что именно пошло не так и где надо поправить. Начать стоит не с установки через инсталлятор а с бинарной сборки, вроде для Windows их тоже выкладывали в виде простого .zip архива.
Если не найдете - у инсталляторов что MSI что InstallShield есть специальные ключи запуска для распаковки приложения из инсталлятора, без самой установки.
ris58h
Патч с одним только
PathManager.class
не сработает?alex0x08 Автор
К сожалению нет тк есть вложенные классы. И подмена .class файлов (или манипуляции с байткодом) в оригинальном JAR тоже не сработают тк Idea при сборке подписывает все свои файлы и проверяет в рантайме.
Yami-no-Ryuu
Так в classpath можно указать и каталог.
Ну и в догонку, есть curl -O , сохраняющая имя файла.
alex0x08 Автор
Можно, но очень уж криво будет выглядеть, поскольку файл класса получается не один (внутри есть вложенные). Хотелось все же показать универсальное и повторяемое решение.