software-development
July 27, 2023

Чиним IntelliJ IDEA под *BSD

В один прекрасный летний день 2023го, разработчики этой замечательной IDE выкатили обновление, в составе которого появилась нативная неотключаемая библиотека. Результат можете наблюдать на скриншоте ниже.

Рассказываю как починить.

Breaking News

Пока я все это чинил и писал, появился официальный способ решения данной проблемы:

Please use VM property idea.ui.icons.svg.disk.cache=false as a workaround. Help -> Edit Custom VM options... -> add new line

-Didea.ui.icons.svg.disk.cache=false

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

Баг

Вот полный текстовый трейс, взятый из лога среды разработки:

2023-08-13 09:32:54,512 [    101]   INFO - STDERR - java.lang.AssertionError: Cannot create SvgCacheManager
2023-08-13 09:32:54,513 [    102]   INFO - STDERR - 	at com.intellij.openapi.diagnostic.DefaultLogger.error(DefaultLogger.java:54)
2023-08-13 09:32:54,513 [    102]   INFO - STDERR - 	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:419)
2023-08-13 09:32:54,514 [    103]   INFO - STDERR - 	at com.intellij.ui.svg.SvgCacheManagerKt.createSvgCacheManager(SvgCacheManager.kt:62)
2023-08-13 09:32:54,515 [    104]   INFO - STDERR - 	at com.intellij.ui.svg.SvgCacheManagerKt$createSvgCacheManager$1.invokeSuspend(SvgCacheManager.kt)
2023-08-13 09:32:54,515 [    104]   INFO - STDERR - 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-08-13 09:32:54,516 [    105]   INFO - STDERR - 	at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:32)
2023-08-13 09:32:54,517 [    106]   INFO - STDERR - 	at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
2023-08-13 09:32:54,517 [    106]   INFO - STDERR - 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
2023-08-13 09:32:54,518 [    107]   INFO - STDERR - 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
2023-08-13 09:32:54,518 [    107]   INFO - STDERR - 	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
2023-08-13 09:32:54,519 [    108]   INFO - STDERR - 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
2023-08-13 09:32:54,519 [    108]   INFO - STDERR - 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
2023-08-13 09:32:54,520 [    109]   INFO - STDERR - 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
2023-08-13 09:32:54,520 [    109]   INFO - STDERR - Caused by: java.lang.UnsatisfiedLinkError: /opt/app/idea-IC-232.8660.185/lib/native/linux-x86_64/libsqliteij.so: /opt/app/idea-IC-232.8660.185/lib/native/linux-x86_64/libsqliteij.so: wrong number of segments (4 != 2)
2023-08-13 09:32:54,521 [    110]   INFO - STDERR - 	at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
2023-08-13 09:32:54,522 [    111]   INFO - STDERR - 	at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:388)
2023-08-13 09:32:54,522 [    111]   INFO - STDERR - 	at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:232)
2023-08-13 09:32:54,523 [    112]   INFO - STDERR - 	at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:174)
2023-08-13 09:32:54,523 [    112]   INFO - STDERR - 	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2389)
2023-08-13 09:32:54,524 [    113]   INFO - STDERR - 	at java.base/java.lang.Runtime.load0(Runtime.java:755)
2023-08-13 09:32:54,524 [    113]   INFO - STDERR - 	at java.base/java.lang.System.load(System.java:1953)
2023-08-13 09:32:54,525 [    114]   INFO - STDERR - 	at org.jetbrains.sqlite.SqliteLibLoaderKt.loadSqliteNativeLibrary(sqliteLibLoader.kt:42)
2023-08-13 09:32:54,525 [    114]   INFO - STDERR - 	at org.jetbrains.sqlite.SqliteLibLoaderKt.loadNativeDb(sqliteLibLoader.kt:30)
2023-08-13 09:32:54,526 [    115]   INFO - STDERR - 	at org.jetbrains.sqlite.SqliteConnection.<init>(SqliteConnection.kt:32)
2023-08-13 09:32:54,526 [    115]   INFO - STDERR - 	at org.jetbrains.sqlite.SqliteConnection.<init>(SqliteConnection.kt:21)
2023-08-13 09:32:54,527 [    116]   INFO - STDERR - 	at com.intellij.ui.svg.SvgCacheManagerKt.connectToSvgCache(SvgCacheManager.kt:76)
2023-08-13 09:32:54,527 [    116]   INFO - STDERR - 	at com.intellij.ui.svg.SvgCacheManagerKt.access$connectToSvgCache(SvgCacheManager.kt:1)
2023-08-13 09:32:54,528 [    117]   INFO - STDERR - 	at com.intellij.ui.svg.SvgCacheManagerKt$createSvgCacheManager$2$1.invokeSuspend(SvgCacheManager.kt:53)
2023-08-13 09:32:54,528 [    117]   INFO - STDERR - 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2023-08-13 09:32:54,528 [    117]   INFO - STDERR - 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
2023-08-13 09:32:54,529 [    118]   INFO - STDERR - 	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
2023-08-13 09:32:54,529 [    118]   INFO - STDERR - 	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:100)
2023-08-13 09:32:54,530 [    119]   INFO - STDERR - 	... 4 more
2023-08-13 09:32:54,530 [    119]   INFO - STDERR - 

Багофича

Конечно же я такой не один и сразу после этого релиза вылезло много людей, использующих продукт Intellij в самых экзотических ОС.

Официально *BSD не поддерживаются, но Idea вполне неплохо на них работала. Устаревшие версии этой IDE еще присутствуют в портах, но лишь в виде уже готовой сборки.

Но полная сборка среды ни под одной из BSD не поддерживается.

Вообще в Idea достаточно много нативных бибилотек, но до последнего патча это не мешало — просто валились ошибки о неподдерживаемой ОС при запуске среды.

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

Поскольку Intellij выкатывают релизы чаще чем кошки плодятся, поддерживать сборку под *BSD у них явно нет лишних рук.

Моих двух рук к сожалению тоже недостаточно чтобы полноценно поддерживать полную сборку под FreeBSD, поэтому ниже решение «на сегодняшний день», которое может сломаться или стать неактуальным в скором времени.

Как чинить

На наше девелоперское счастье, Idea - открытый проект, исходники которого публично доступны в Github, поэтому решение заключается в выкачивании исходников, сборки под FreeBSD этой конкретной библиотеки и подкладывании ее вручную в каталог с установленной Intellij Idea.

Забираем:

git clone --depth 1 https://github.com/JetBrains/intellij-community.git

Исходников много, поэтому выкачиваем лишь одну ветку, без истории изменений.

Нужный нам проект находится в каталоге:

platform/sqlite

Полностью его мы собирать не будем, поскольку помимо нативной части он содержит еще и биндинги на Java, с которыми все хорошо.

Скриптов сборки два: build.sh — стартовый, содержит обертку для запуска в докере, make.sh — сама сборка нативной библиотеки.

Вот make.sh нам и нужен, его и будем править.

Что нужно добавить:

Само ветвление для FreeBSD:

elif [ "$OS" == "freebsd" ]; then

Переопределить пути до LLVM, который используется для сборки:

export LDFLAGS="-L/usr/local/llvm15/lib"
export CPPFLAGS="-I/usr/local/llvm15/include"

Поскольку при сборке на MacOS использовалась 15я версия LLVM — я использовал ее же на FreeBSD.

Добавить путь к системным библиотекам:

cFlags+=" -isystem /usr/local/include"

Ниже полный исходник поправленного скрипта сборки, с добавлением поддержки FreeBSD:

#!/usr/bin/env bash
# Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
set -ex

outDir="target/sqlite/$OS-$ARCH"
rm -rf "${outDir:?}/*"
mkdir -p "$outDir"

# brew install llvm
# use latest CLang 15 instead of 14 for a smaller binaries
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
export LDFLAGS="-L/opt/homebrew/opt/llvm/lib"
export CPPFLAGS="-I/opt/homebrew/opt/llvm/include"

cFlags="-O3 -fPIC -Isqlite -fvisibility=hidden -Wno-implicit-function-declaration"
linkFlags="-Wl,-S,-x"
libFilename="so"
if [ "$OS" == "mac" ]; then
  cFlags+=" -mmacosx-version-min=10.14"
  linkFlags="-dynamiclib -fuse-ld=lld "
  libFilename="libsqliteij.jnilib"

  if [ "$ARCH" == "x86_64" ]; then
    cFlags+=" --target=x86_64-apple-darwin18.7.0"
  fi
# our FreeBSD support  
elif [ "$OS" == "freebsd" ]; then
  export LDFLAGS="-L/usr/local/llvm15/lib"
  export CPPFLAGS="-I/usr/local/llvm15/include"
  libFilename="libsqliteij.so"
  linkFlags+=" -shared"
  cFlags+=" -isystem /usr/local/include"

elif [ "$OS" == "linux" ]; then
  libFilename="libsqliteij.so"

  # cannot compile arm - unable to find library -lgcc, so, use dock cross
  if [ "$ARCH" == "aarch64" ]; then
    linkFlags+=" -shared"
  else
    cFlags+=" --target=$ARCH-unknown-linux-gnu --sysroot=target/linux-$ARCH"
    linkFlags+=" -shared -fuse-ld=lld"
  fi
elif [ "$OS" == "win" ]; then
  linkFlags="-Wl,--kill-at -shared -static-libgcc"
  libFilename="sqliteij.dll"
fi

CC="${CC:-clang}"

#linkFlags+=" -fuse-ld=lld"
"${CROSS_PREFIX}${CC}" -o "$outDir/sqlite3.o" -c $cFlags \
  -DSQLITE_DQS=1 \
  -DSQLITE_THREADSAFE=1 \
  -DSQLITE_DEFAULT_MEMSTATUS=0 \
  -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
  -DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
  -DSQLITE_MAX_EXPR_DEPTH=0 \
  -DSQLITE_OMIT_DECLTYPE \
  -DSQLITE_OMIT_DEPRECATED \
  -DSQLITE_OMIT_PROGRESS_CALLBACK \
  -DSQLITE_OMIT_SHARED_CACHE \
  -DSQLITE_USE_ALLOCA \
  -DSQLITE_OMIT_AUTOINIT \
  -DSQLITE_HAVE_ISNAN \
  -DHAVE_USLEEP=1 \
  -DSQLITE_TEMP_STORE=2 \
  -DSQLITE_DEFAULT_CACHE_SIZE=2000 \
  -DSQLITE_CORE \
  -DSQLITE_ENABLE_FTS5 \
  -DSQLITE_ENABLE_STAT4 \
  -DSQLITE_MAX_MMAP_SIZE=1099511627776 \
  \
  sqlite/sqlite3.c

"${CROSS_PREFIX}${CC}" -o "$outDir/NativeDB.o" -c $cFlags -I"sqlite/$OS" sqlite/NativeDB.c

libFile="$outDir/$libFilename"
"${CROSS_PREFIX}${CC}" $cFlags -o "$libFile" "$outDir/NativeDB.o" "$outDir/sqlite3.o" $linkFlags
shasum -a 256 "$libFile" | head -c 64 >"$libFile.sha256"

unlink "$outDir/sqlite3.o"
unlink "$outDir/NativeDB.o"

Также нужно будет создать каталог с "вычисляемым" заголовком JNI:

mkdir sqlite/freebsd && cp sqlite/linux/jni_md.h sqlite/freebsd

И запустить сборку.

Запускается скрипт вот так (необходимо использовать bash):

OS=freebsd ARCH=x86_64 ./make.sh 

Результат сборки выглядит вот так:

Готовая нативная библиотека будет в каталоге:

target/sqlite/freebsd-x86_64

Ее необходимо скопировать в каталог в котором установлена (распакована среда разработки):

cp target/sqlite/freebsd-x86_64/libsqliteij.so /opt/app/idea-IC-232.8660.185/lib/native/linux-x86_64/

И наконец запустить:

И продолжать наслаждаться этой замечательной средой разработки.

Unable to save plugin settings

При первом запуске и открытии либо создании нового проекта у вас будут сыпаться вот такие ошибки:

Виновник этого празника — плагин «Code With Me», который нужен для реальной работы примерно как пятая нога.

Казалось бы и хер с ним, но из-за этих ошибок среда разработки не сохраняет настройки и сбивает состояние на начальное — вместо последнего открытого проекта у вас постоянно будет открываться стартовая страница среды разработки.

Что сильно мешает.

Поэтому это чудо инженерной мысли нужн отключить:

Добавление про OpenBSD

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

elif [ "$OS" == "openbsd" ]; then
  libFilename="libsqliteij.so"
  linkFlags+=" -shared"
  cFlags+=" -isystem /usr/local/include"

Запуск сборки:

OS=openbsd ARCH=x86_64 ./make.sh

Дальше готовая библиотека все также копируется в дистрибьютив Idea и она начинает запускаться:

Добавление для NetBSD

Шаги для исправления полностью аналогичны описанному выше методу для OpenBSD, в make.sh добавляем:

elif [ "$OS" == "netbsd" ]; then
  libFilename="libsqliteij.so"
  linkFlags+=" -shared"
  cFlags+=" -isystem /usr/local/include"

Запускаем:

OS=netbsd ARCH=x86_64 ./make.sh

Точно также копируем в заменой в папку lib/native/linux-x86_64/ в распакованном дистрибьютиве Intellij Idea и запускаем.

Вот так оно выглядит в работе:

Таким образом удалось починить и продолжить использование свежих версий Intellij Idea под всеми тремя вариациями BSD и никто при этом даже не умер.