Detektyw: update-java-alternatives i paczki snap
...czyli weekendowe śledztwo dlaczego Netbeans zainstalowany ze snap'a używa Javy innej niż domyślna.
Tło historii
Na jednej z maszyn mam zainstalowane kilka różnych wersji Javy:
$ update-java-alternatives -l
ibm-java80-jdk-x86_64 80 /usr/lib/jvm/ibm-java80-jdk-x86_64
ibm-java80-jre-x86_64 80 /usr/lib/jvm/ibm-java80-jre-x86_64
java-1.11.0-openjdk-amd64 1111 /usr/lib/jvm/java-1.11.0-openjdk-amd64
java-1.8.0-openjdk-amd64 1081 /usr/lib/jvm/java-1.8.0-openjdk-amd64
java-1.9.0-openjdk-amd64 1091 /usr/lib/jvm/java-1.9.0-openjdk-amd64
W tym domyślną jdk8:
$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.16.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
Mam też aktualną wersję Netbeans zainstalowaną ze snap'a. Niestety podczas uruchamiania Netbeans używa Javy IBM (której?) i wywala się z takim pięknym komunikatem:
JVMJ9VM007E Command-line option unrecognised: --add-opens=java.base/java.net=ALL-UNNAMED
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Oczywiście mogę podać jdk jako parametr z lini komend i Netbeans uruchomia się normalnie, np dla każdej z poniższych (w tym IBMowej) Netbeans działa poprawnie:
netbeans --jdkhome /usr/lib/jvm/java-1.11.0-openjdk-amd64
netbeans --jdkhome /usr/lib/jvm/java-1.8.0-openjdk-amd64
netbeans --jdkhome /usr/lib/jvm/ibm-java80-jdk-x86_64
Hipoteza 1 + Własny snap
Hipoteza pierwsza: snap nie respektuje ustawień
update-java-alternatives
.
Żeby to sprawdzić postanowiłem napisać najprostszy program, który wyświetli dane javy za pomoca której został uruchomiony oraz spakować go do własnego snap'a.
Kod w javie:
public class Main {
public static void main(String[] args) {
System.out.println(System.getProperty("java.home"));
System.out.println(System.getProperty("java.vendor"));
System.out.println(System.getProperty("java.vendor.url"));
System.out.println(System.getProperty("java.version"));
}
}
Budowanie snap'a
Temat budowania snapów nadaje się na osobny post, teraz będzie tylko skrócona wersja ;)
- Do budowania paczek
snap
służy poleceniesnapcraft
- Definicja paczki powinna być w pliku
<projekt>/snap/snapcraft.yaml
Pierwsza wersja snapcraft.yaml
(powstała na podstawie przykładu dot. javy z oficjalnej dokumentacji):
name: javaversion
version: '0.1'
summary: Checking default java in snaps
description: |
Checking default java in snaps
So I can test what's wrong with netbeans
confinement: devmode
apps:
javaversion:
command: desktop-launch $SNAP/snapjavasample-0.1/bin/snapjavasample
parts:
javaversion:
after: [desktop-glib-only]
plugin: gradle
source: .
override-build: |
export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"
./gradleW distZip
unzip build/distributions/snapjavasample-*.zip -d $SNAPCRAFT_PART_INSTALL/
build-packages:
- unzip
- openjdk-8-jdk
Budujemy:
snapcraft build --debug
Instalujemy:
snap install javaversion_0.1_amd64.snap --devmode --dangerous
...i sprawdzamy:
$ javaversion
/snap/javaversion/x1/usr/lib/jvm/java-8-openjdk-amd64/jre
Oracle Corporation
http://java.oracle.com/
1.8.0_191
wtf? Nie do końca o to mi chodziło. Ewidentnie javę mamy w snapie. Sprawdźmy:
$ unsquashfs -l javaversion_0.1_amd64.snap |grep "java-8-openjdk-amd64/jre/bin/java"
squashfs-root/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
$ ls -lh javaversion_0.1_amd64.snap
-rw-r--r-- 1 ihsahn ihsahn 88M maj 5 13:50 javaversion_0.1_amd64.snap
Plik snap ma rozmiar 88M i zawiera całe openjdk... czyli inaczej niż Netbeans.
Otóż okazuje się, że można podać snapcraftowi, które pliki powinien brać pod uwagę podczas tworzenia paczki: filesets
Dla pewności zmieniłem też confinment
na taki jakiego używa Netbeans: classic
Poprawiony snapcraft.yaml
:
name: javaversion
version: '0.1'
summary: Checking default java in snaps
description: |
Checking default java in snaps
So I can test what's wrong with netbeans
confinement: classic
grade: devel
architectures: [ amd64 ]
apps:
javaversion:
command: snapjavasample-0.1/bin/snapjavasample
parts:
javaversion:
plugin: gradle
source: .
filesets:
javaversion: [snapjavasample-0.1/*]
override-build: |
export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"
./gradlew distZip
unzip build/distributions/snapjavasample-*.zip -d $SNAPCRAFT_PART_INSTALL/
stage:
- $javaversion
build-packages:
- unzip
- openjdk-8-jdk
Przez zbudowaniem trzeba jeszcze wyczyścić cache:
snapcraft clean javaversion -s pull
...i usunąć starą paczkę (bo nie zmieniłem wersji):
snap remove javaversion
Potem ponownie
snapcraft build --debug
,
oraz install (tym razem z --classic
zamiast -devmode
)
snap install javaversion_0.1_amd64.snap ---dangerous --classic
...i sprawdzamy:
/usr/lib/jvm/java-8-openjdk-amd64/jre
Oracle Corporation
http://java.oracle.com/
1.8.0_191
Hipoteza upadła: nowo wyprodukowany snap widzi poprawnie domyślną javę...
Przy okazji: nowy snap (bez javy) "waży" już tylko 8kb ...
Testowy program wraz z definicja snap'a
Hipoteza 2 + skyrpty uruchamiające Netbeans
Hipoteza druga: Netbeans podczas uruchamiania sam znajduje jakąś lewą javę
Ok, najpierw trzeba zobaczyć co się odpala dokładnie w snap'ie: według
źródłowego pliku snapcraft
jest to netbeans/bin/netbeans
. Czyli standardowy skrypt wykonujący Netebans'a, zobaczymy co powie nam bash w trybie "debug".
Żeby oczywiście wszystko miało sens, wykonanie komendy powinno się odbywać w takim samym środowisku jak działa snap:
dla każdej zainstalowanej paczki możemy "zalogować" się do powłoki, w tym przypadku będzie to: snap run --shell netbeans
i potem już z górki:
$ cd /snap/netbeans/current/netbeans/bin
bash -x netbeans
...i fragment ostatniej linijki
++ exec /bin/bash /snap/netbeans/current/netbeans/platform/lib/nbexec --userdir /data/11.0 --cachedir /cache/11.0 --jdkhome '' --branding nb --clusters ...
interesujący nas fragment: --jdkhome ''
- czyli na tym etapie nie ma jeszcze rozpoznanej wersji javy.
nbexec
również jest plikiem shell, szybkie przeglądniecie kodu i mamy następujący fragment
javac=`which javac`
if [ -z "$javac" ] ; then
java=`which java`
if [ ! -z "$java" ] ; then
java=`resolve_symlink "$java"`
jdkhome=`dirname $java`"/.."
fi
else
javac=`resolve_symlink "$javac"`
jdkhome=`dirname $javac`"/.."
fi
Czyli jeśli Netbeans znajdzie javac
to używa jej ścieżki, a jeśli nie, to szuka java
. hm..
# javac -version
javac 1.7.0
?! Wygląda na to, że update-java-alternatives
nie zmieniało wskazań na javac i to już od.. dawna.
Jeszcze jeden test żeby się upewnić:
$ update-alternatives --get-selections |grep javac
javac auto /usr/lib/j2sdk1.7-ibm/bin/javac
No i mamy winnego!
update-java-alternatives
W debianie/Ubuntu domyślną javę można ustawiać zbiorczo (dla wszystkich komend związanych z javą) poprzez
update-java-alternatives
(które pod spodem woła update-alternatives
) albo ręcznie dla każdej komendy poprzez update-alternatives
.
Żeby update-java-alternatives
mogło przełączyć zainstalowane jre
/jdk
- taka instalacja musi zawierać też ukryty
plik *.jinfo
. Plik taki definiuje jakie komendy obsługuje dana instalacja oraz ich lokalizacje. Przykładowo dla OpenJdk 1.8.0:
$ cat /usr/lib/jvm/.java-1.8.0-openjdk-amd64.jinfo
name=java-8-openjdk-amd64
alias=java-1.8.0-openjdk-amd64
priority=1081
section=main
hl rmid /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/rmid
hl java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
hl keytool /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/keytool
hl jjs /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/jjs
hl pack200 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/pack200
hl rmiregistry /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/rmiregistry
hl unpack200 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/unpack200
hl orbd /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/orbd
hl servertool /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/servertool
hl tnameserv /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/tnameserv
hl jexec /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jexec
jre policytool /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/policytool
jdkhl idlj /usr/lib/jvm/java-8-openjdk-amd64/bin/idlj
jdkhl jdeps /usr/lib/jvm/java-8-openjdk-amd64/bin/jdeps
jdkhl wsimport /usr/lib/jvm/java-8-openjdk-amd64/bin/wsimport
jdkhl rmic /usr/lib/jvm/java-8-openjdk-amd64/bin/rmic
jdkhl jinfo /usr/lib/jvm/java-8-openjdk-amd64/bin/jinfo
jdkhl jsadebugd /usr/lib/jvm/java-8-openjdk-amd64/bin/jsadebugd
jdkhl native2ascii /usr/lib/jvm/java-8-openjdk-amd64/bin/native2ascii
jdkhl jstat /usr/lib/jvm/java-8-openjdk-amd64/bin/jstat
jdkhl javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac
jdkhl javah /usr/lib/jvm/java-8-openjdk-amd64/bin/javah
jdkhl jps /usr/lib/jvm/java-8-openjdk-amd64/bin/jps
jdkhl jstack /usr/lib/jvm/java-8-openjdk-amd64/bin/jstack
jdkhl jrunscript /usr/lib/jvm/java-8-openjdk-amd64/bin/jrunscript
jdkhl javadoc /usr/lib/jvm/java-8-openjdk-amd64/bin/javadoc
jdkhl javap /usr/lib/jvm/java-8-openjdk-amd64/bin/javap
jdkhl jar /usr/lib/jvm/java-8-openjdk-amd64/bin/jar
jdkhl extcheck /usr/lib/jvm/java-8-openjdk-amd64/bin/extcheck
jdkhl schemagen /usr/lib/jvm/java-8-openjdk-amd64/bin/schemagen
jdkhl xjc /usr/lib/jvm/java-8-openjdk-amd64/bin/xjc
jdkhl jmap /usr/lib/jvm/java-8-openjdk-amd64/bin/jmap
jdkhl jstatd /usr/lib/jvm/java-8-openjdk-amd64/bin/jstatd
jdkhl jhat /usr/lib/jvm/java-8-openjdk-amd64/bin/jhat
jdkhl jdb /usr/lib/jvm/java-8-openjdk-amd64/bin/jdb
jdkhl serialver /usr/lib/jvm/java-8-openjdk-amd64/bin/serialver
jdkhl wsgen /usr/lib/jvm/java-8-openjdk-amd64/bin/wsgen
jdkhl jcmd /usr/lib/jvm/java-8-openjdk-amd64/bin/jcmd
jdkhl jarsigner /usr/lib/jvm/java-8-openjdk-amd64/bin/jarsigner
jdk appletviewer /usr/lib/jvm/java-8-openjdk-amd64/bin/appletviewer
jdk jconsole /usr/lib/jvm/java-8-openjdk-amd64/bin/jconsole
plugin mozilla-javaplugin.so /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/IcedTeaPlugin.so
Skoro plik jest i jest poprawny (tak wygląda) dlaczego więc update-java-alternatives
nie przełączyło javac?
Bo ktoś w międzyczasie zmienił tag z jdk
na jdkhl
w plikach *.jinfo
którego update-java-laternatives
(w tej wersji) nie obsługuje
Problem jest chyba dośc powszechny, bo jak już wiadomo czego szukać można trafić na sporo podobnych problemów:
itd itp...
Dlaczego gradle
działa pomimo tego?
Ano dlatego, że w przeciwieństwie do netbeans szuka komendy java
i zakłada, że w tym samym drzewie będzie też
javac
(w przypadku gdy mamy tam tylko jre
trzeba mu ekstra podać mu ścieżkę do jdk)
Rozwiązanie problemu z Netbeans
Zmiana konfiguracji Netbeans
Czyli lokalne nadpisanie domyślnej konfiguracji:
mkdir ~/snap/netbeans/common/data/11.0/etc/
echo 'netbeans_jdkhome="/usr/lib/jvm/java-8-openjdk-amd64"' >> ~/snap/netbeans/common/data/11.0/etc/netbeans.conf
w ten sposób uniezależniamy się też od innych błędów związanych z update-java-alternatives
albo od zmian domyślnej javy w ogóle.
Naprawa update-java-alternatives
Czyli przywrócenie rozpoznawalnych tag'ów w pliku *.jinfo
sudo sed -i 's/^hl/jre/p' /usr/lib/jvm/.java-1.8.0-openjdk-amd64.jinfo
sudo sed -i 's/^jdkhl/jdk/p' /usr/lib/jvm/.java-1.8.0-openjdk-amd64.jinfo
Potem już tylko na nowo trzeba ustawić domyślną javę:
$ sudo update-java-alternatives -s java-1.8.0-openjdk-amd64
i test...
$ javac -version
javac 1.8.0_191
voila!
Literatura
- The snapcraft format
- Testowy program wraz z definicją snap'a
- Odpowiedź z AskUbuntu dotycząca podobnego problemu