Kako sami kompajlirati JDK
Mnogi kolege koji se bave Java razvojem, iz dana u dan pišu Java kod, a taj rad sigurno ne može bez osnovnog Java okruženja — JDK-a, jer naš Java kod na kraju radi na JVM virtuelnoj mašini.
Uobičajeno je da pre učenja Java-e prvi korak bude instalacija JDK okruženja. To je jednostavno — uglavnom direktno preuzmemo JDK sa zvaničnog sajta, instaliramo, podesimo promenljive okruženja i već veslo možemo da koristimo.
Ali, da se ovako redovno koristimo njime, zar zaista nećemo da budemo radoznali kako se ta stvar zapravo dobija kompajliranjem izvornog koda?
Sa tim osnovnim pitanjem, danas ćemo se ozbiljno prihvatiti posla i svojim rukama kompajlirati JDK koji će biti samo naš!
Priprema okruženja
Najpre da kažemo: odnos verzija softvera pre kompajliranja izuzetno je važan. Pri snalaženju sa problemima, sve one neobične pojave gotovo su u vezi sa tim; kada sam kasnije uskladio verzije, sve je išlo prilično glatko.
Hajde da pređemo u red i razjasnimo koje okruženje i alate treba da pripremimo za kompajliranje JDK-a.
1. boot JDK
Da bismo kompajlirali JDK, na našoj mašini mora prethodno biti instaliran jedan JDK — zvanično se naziva bootstrap JDK (ili boot JDK).
Na primer, ako želimo da kompajliramo JDK 8, na mašini mora biti barem JDK 7 ili novija verzija; ako želimo JDK 11, na mašini moraju biti instalirani JDK 10 ili 11.
Znači, opet koka ili jaje...
2. Unix okruženje
Za kompajliranje JDK-a potrebna je podrška Unix okruženja!
To je na Linux i macOS operativnim sistemima već prirodno obezbeđeno, dok je za Windows braću nešto komplikovanije — potrebno je simulirati ga preko softvera poput Cygwin ili MinGW/MSYS.
Kao što kaže zvanično: na Linux platformi kompajliranje JDK-a obično ima najmanje problema i lako uspe; macOS je odmah iza; na Windows-u treba uložiti malo više truda, a problema može biti i više.
Koren uzroka jeste u tome što Windows ipak nije Unix-Like sistem; mnoge softverske osnove kompajliranja ne mogu bez Unix Toolkit-a, pa je zato i komplikovaniji.
3. Kompajler / alatni lanac kompajliranja
Ispod haube JDK izvorni kod (posebno deo za JVM virtuelnu mašinu) uglavnom je pisan u C++/C-u, pa tako ni odgovarajući kompajleri ne mogu da se izbegnu.
Podrška za kompajlere na različitim platformama data je u tabeli ispod — izaberite prema svojoj platformi.
| Operativni sistem | Podržani alatni lanac |
|---|---|
| Linux | gcc, clang |
| macOS | Apple Xcode (koristi clang) |
| Solaris | Oracle Solaris Studio |
| AIX | IBM XL C/C++ |
| Windows | Microsoft Visual Studio |
4. Ostali alati
Tipični predstavnici:
Autoconf: alatka za automatsku konfiguraciju softverskih paketa izvornog kodaMake: alatka za kompajliranje i izgradnjufreetype: besplatna biblioteka za renderovanje; nju eventualno koristi deoJDK-a zadužen za grafiku
Dakle, to je to za pregled okruženja. Sada da konkretno navedem detaljne informacije o verzijama softvera koje sam koristio pri kompajliranju JDK 8 i JDK 11:
Pri kompajliranju JDK-a 8:
Operativni sistem: macOS 10.11.6boot JDK: JDK 1.8.0 (build 1.8.0_201-b09)Verzija Xcode-a: 8.2Kompajler: Version 8.0.0 (at /usr/bin/clang)
Pri kompajliranju JDK-a 11:
Operativni sistem: macOS 10.15.4boot JDK: JDK 11.0.7 (build 11.0.7+8-LTS)Verzija Xcode-a: 11.5Kompajler: Version 11.0.3 (at /usr/bin/clang)
Ako ti pri kompajliranju iskrsne mnogo problema, velika je verovatnoća da je neki softver nedostaje ili da se verzije ne podudaraju; nemojte lako odustati, strpljivo proveri.
Preuzimanje izvornog koda JDK-a
Preuzimanje izvornog koda JDK-a moguće je na dva načina.
Način 1: preko alata Mercurial
Mercurial je lagana distribuirana sistema za kontrolu verzija (implementirana u Pythonu), po ulozi vrlo slična Git-u; nakon instalacije dobijate komandu hg.
OpenJDK izvorni kod je unapned hostovan na http://hg.openjdk.java.net/.
Tako, na primer, za preuzimanje JDK 8 dovoljno je da uradite hg clone, isto kao git clone:
hg clone [http://hg.openjdk.java.net/jd...](https://link.segmentfault.com/?enc=Snt8gNbYV7nkV3etTe%2FGJw%3D%3D.7IrUNCuc0HOEyvjCiCBOPMEBJ09bjLifieJi0I7iwtuuIeYUdSfCkC9c4D7z9wdq)
Isto tako, za preuzimanje JDK 11:
hg clone [http://hg.openjdk.java.net/jd...](https://link.segmentfault.com/?enc=BnHqAYXzfRcVfPgGgo1yOw%3D%3D.011np6%2FiCLuojl%2FBtvROkTVXr0PSdMYcYpAg2WUIE045BEFIrbCNAD42vWwIUb3d)
Međutim, brzina preuzimanja na ovaj način nije naročito velika.
Način 2: direktno preuzimanje unapred pripremljenog izvornog paketa
Adresa za preuzimanje: https://jdk.java.net/ — na njoj se nalaze i zvanični i rani ("early access") build-ovi izvornog koda. Izaberi verziju koju želite i preuzmite.
Automatska konfiguracija pre kompajliranja
Kada preuzmete paket izvornog koda, smesti ga u neki lokalni direktorijum (preporučujem čisto englesku putanju, da izbegnete nepotrebne nevolje), raspakuj ga, zatim uđi u koreni direktorijum izvornog koda i izvrši:
sh configure
Naravno, ovde se pokreću podrazumevane stavke konfiguracije.
Ovaj korak obavlja niz poslova automatske konfiguracije, obično vrlo brzo; ako se na kraju pojavi očekivani ispis, srećni ste — konfiguracija pre kompajliranja je završena!
Evo kako je to izgledalo kada sam konfigurisao JDK 11, odnosno JDK 8:
Konfiguracija JDK-a 8 završena:
====================================================
A new configuration has been successfully created in
/Users/codesheep/Downloads/openjdk8/build/macosx-x86_64-normal-server-release
using normal settings.
Configuration summary:
* Debug level: release
* JDK variant: normal
* JVM variants: server
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
Tools summary:
* Boot JDK: java version 1.8.0_201
* C Compiler: clang version 8.0.0 (at /usr/bin/clang)
====================================================Konfiguracija JDK-a 11 završena:
====================================================
A new configuration has been successfully created in
/Users/codesheep/Downloads/openjdk11/build/macosx-x86_64-normal-server-release
using default settings.
Configuration summary:
* Debug level: release
* HS debug level: product
* JVM variants: server
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
* Version string: 11-internal (+0)
Tools summary:
* Boot JDK: openjdk 11.0.7 (build 11.0.7+8-LTS)
* C Compiler: clang version 11.0.3 (at /usr/bin/clang)
====================================================Napomena: Ako ovaj korak prijavi grešku, velika je verovatnoća da neki softverski paket nedostaje ili da je instaliran, ali se verzija ne podudara; u log ispisu na konzoli obično stoji odgovarajuća poruka.
Na primer, pri konfiguraciji JDK 8 naišao sam na problem error: GCC compiler is required:
configure: error: GCC compiler is required. Try setting --with-tools-dir.
configure: exiting with result code 1Iako je u sistemu već postojao kompajler, greška se i dalje javljala. Kasnije sam izmenom fajla koreni direktorijum JDK izvornog koda/common/autoconf/generated-configure.sh zakomentarisao dva vezana reda i konfiguracija je prošla:
# common/autoconf/generated-configure.sh
# as_fn_error $? "GCC compiler is required. Try setting --with-tools-dir." "$LINENO" 5Kada je konfiguracija završena, krećemo u stvarno kompajliranje!
Pravo kompajliranje
Ovde radimo potpuno kompajliranje; direktno u korenom direktorijumu preuzetog JDK izvornog koda izvrši sledeću komandu:
make all
Ovaj korak zahteva izvesno vreme, pa strpljivo sačekaj. Ako pri kompajliranju nastupi greška, ono se prekida; ako vidite sledeće dve slike, čestitam — uspešno ste kompajlirali JDK izvorni kod, možete da uživate uz šoljicu kafe.
Kompajliranje JDK-a 8 završeno:
----- Build times -------
Start 2020-05-27 19:00:05
End 2020-05-27 23:24:13
00:00:29 SETUP
04:24:08 TOTAL
-------------------------
Finished building OpenJDK for target 'all'Kompajliranje JDK-a 11 završeno:
Stopping jdk image
Stopping sjavac server
Finished building target 'all' in configuration 'macosx-x86_64-normal-server-release'Iz poređenja dve slike vidi se da izlaz na kraju kompajliranja JDK 8 i JDK 11 ipak razlikuje. Razlika u vremenu u velikoj meri potiče od toga što je mašina za kompajliranje JDK 11 bila znatno moćnija.
Provera rezultata
Nakon što se kompajliranje JDK izvornog koda završi, sigurno će se generisati i ispisati mnogo artefakata — a to upravo željno iščekujemo da vidimo.
Pošto se organizacija strukture izvornog paketa JDK 8 i JDK 11 razlikuje, razlikuju se i sadržaj i lokacija ispisa. Pregledajmo ih redom.
1. Ispis kompajliranja JDK-a 8
Po završetku kompajliranja, u direktorijumu build kreira se direktorijum macosx-x86_64-normal-server-release i u njemu se nalaze svi rezultati kompajliranja.
Pre svega, izvršni program Java može se pronaći u sledećem direktorijumu:
jdk koreni direktorijum izvornog koda/build/macosx-x86_64-normal-server-release/jdk/bin
Nakon što uđete u taj direktorijum, možete uneti komandu ./java -version za proveru:
$ pwd
/Users/codesheep/Downloads/openjdk8/build/macosx-x86_64-normal-server-release/jdk/bin
$ ./java -version
openjdk version "1.8.0-internal"
OpenJDK Runtime Environment (build 1.8.0-internal-codesheep_2020_05_27_19_44-b00)
OpenJDK 64-Bit Server VM (build 25.71-b00, mixed mode)Zatim, gotovi JDK komplet dobijen kompajliranjem može se pronaći u direktorijumu jdk koreni direktorijum izvornog koda/build/macosx-x86_64-normal-server-release/images, a sadržaj izgleda ovako:
$ ls build/macosx-x86_64-normal-server-release/images
j2re-bundle j2re-image j2sdk-bundle j2sdk-image lib sec-bin.zip src src.zipgde su:
j2sdk-image: JDK dobijen kompajliranjemj2re-image: JRE dobijen kompajliranjem
Kada uđete u direktorijum j2sdk-image, videćete da je sadržaj isti kao u JDK kompleti koje inače preuzimamo s interneta:
$ ls images/j2sdk-image
ADDITIONAL_LICENSE_INFO ASSEMBLY_EXCEPTION LICENSE THIRDPARTYREADME
bin demo include jre lib man release sample src.zip2. Ispis kompajliranja JDK-a 11
Način organizacije direktorijuma izvornog koda JDK-a 11 već se razlikuje od JDK-a 8; artefakti dobijeni kompajliranjem donekle se razlikuju od ispisa kompajliranja JDK-a 8, ali ne i previše.
Po završetku kompajliranja JDK 11, takođe se u direktorijumu build kreira direktorijum macosx-x86_64-normal-server-release, u kojem se nalaze svi rezultati kompajliranja.
Izvršni program Java dobijen kompajliranjem može se videti u direktorijumu
Koreni direktorijum JDK izvornog koda/build/macosx-x86_64-normal-server-release/jdk/bin
i nakon ulaska u njega možete uneti ./java -version za proveru:
$ pwd
/Users/codesheep/Downloads/openjdk11/build/macosx-x86_64-normal-server-release/jdk/bin
$ ./java -version
openjdk version "11-internal" 2018-09-25
OpenJDK Runtime Environment (build 11-internal+0-2020-05-27-1944-codesheep)
OpenJDK 64-Bit Server VM (build 11-internal+0-2020-05-27-1944-codesheep, mixed mode)Zatim, gotovi JDK 11 komplet dobijen kompajliranjem može se pronaći u direktorijumu Koreni direktorijum JDK izvornog koda/build/macosx-x86_64-normal-server-release/images, a sadržaj izgleda ovako:
$ ls build/macosx-x86_64-normal-server-release/images
docs gengraphs jdk jdk-bundle jmods sec-bin.zip testDirektorijum jdk upravo je gotovi JDK 11 komplet dobijen kompajliranjem.
Korišćenje sopstvenog kompajliranog JDK-a
Pošto smo već sopstvenim rukama kompajlirali gotov `JDK, moramo ga sada i upotrebiti.
Napravi jedan sasvim osnovni Java projekat, npr. pod imenom JdkTest — dovoljna je jedna main klasa koja ispisuje Hello World! — sa ciljem da u njega ugradimo sopstveni kompajlirani JDK.
Otvorimo File → Project Structure, izaberemo karticu SDKs, kliknemo na + → Add JDK i kao JDK home path postavimo direktorijum sopstvenog build-a (.../build/macosx-x86_64-normal-server-release/images/j2sdk-image za JDK 8, odnosno .../images/jdk za JDK 11). Novi SDK zatim izaberemo kao JDK projekta u sekciji Project.
Nakon što kliknemo OK, pokrenimo projekat:
Hello World!
Process finished with exit code 0Vidimo da je sopstveni kompajlirani JDK već u upotrebi.
Povezivanje izvornog koda JDK-a i izmene
Nastavimo u projektu JdkTest iz prethodnog koraka, u Project Structure → SDKs: pod Sourcepath ukloni postojeći src.zip i dodajte src direktorijum iz korenog direktorijuma preuzetog JDK izvornog koda.
Time nam postaje zgodno da sopstveni preuzeti JDK izvorni kod čitamo, debagujemo, menjamo, kao i da u njemu slobodno pravimo beleške i dodajemo komentare.
Najprostiji primer: otvorimo izvorni kod funkcije System.out.println() (klasa PrintStream):
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}Proizvoljno je malo izmenimo — dodajmo dva jednostavna oznaka, ovako:
public void println(String x) {
synchronized (this) {
print("======== By CodeSheep ========\n");
print(x);
newLine();
print("======== By CodeSheep ========\n");
}
}Da bi novi redovi koda stupili na snagu, moramo ponovo u koreni direktorijum JDK izvornog koda, izvršiti make images i time ponovo kompajlirati i generisati JDK:
$ make images
...
Creating jdk image
Stopping sjavac server
Finished building target 'images' in configuration 'macosx-x86_64-normal-server-release'Pošto smo već ranije radili potpuno kompajliranje, inkrementalno kompajliranje pri ponovnom make obično je vrlo brzo.
Nakon ponovnog kompajliranja, ponovo pokrenimo projekat JdkTest i videćemo efekte izmena:
======== By CodeSheep ========
Hello World!
======== By CodeSheep ========
Process finished with exit code 0Problem višelinijskih komentara
Sećam se da smo prilikom postavljanja „Okruženja za čitanje izvornog koda JDK-a” možda primetili problem: čitanje izvornog koda — sasvim je uobičajeno dodavati mu komentare ili beleške! Ali tada je postojao problem: prilikom dodavanja komentara nisu smele da se menjaju strukture redova (komentar je mogao samo na kraju reda, ne i kroz više redova), inače bi pri debagovanju nastupao problem pomeraja brojeva redova.
Razlog je jednostavan: iako smo napravili mapiranje direktorijuma izvornog koda, JDK koji stvarno podržava rad i dalje je onaj prethodno instalirani JDK, a ne onaj koji je ponovo kompajliran na osnovu naših izmena, pa je rešenje ovog problema jednostavno — kao gore, kompajliraj sam JDK.
U praksi se javlja još jedan tipičan problem: kada se dodaju višelinijski komentari na ćirilici/nekoliko jezika, kompajliranje iznenada prijavi grešku!
Na primer, opet uzmimo najjednostavniji izvorni kod System.out.println() iz gornjeg primera i dodajmo nekoliko redova komentara:
public void println(String x) {
synchronized (this) {
/*
* Višelinijski komentar na srpskom:
* prvo ispisujemo string, pa prelazimo u novi red.
*/
print(x);
newLine();
}
}Tada, kad u direktorijumu JDK izvornog koda krenemo u kompajliranje, videćemo ekran prepun ovakvih grešaka:
src/java.base/share/classes/java/io/PrintStream.java:871: error: unmappable character (0x8B) for encoding ascii
src/java.base/share/classes/java/io/PrintStream.java:872: error: unmappable character (0xE6) for encoding ascii
src/java.base/share/classes/java/io/PrintStream.java:873: error: unmappable character (0x54) for encoding ascii
... # desetine istovetnih grešakaMalo me zbunilo, jer sam samo dodao nekoliko redova komentara. Za nas je dodavanje višelinijskih komentara u izvorni kod u suštini potreba, a kompajliranje prijavljuje grešku — pa kako onda uopšte raditi... Tada me prošla jeza po leđima.
Da budem iskren, na tom problemu sam proveo izvesno vreme, ostao budan do kasno u noć. Naposletku, posle pakla, rešio sam ga na sledeći način, pa neka to podelim i s vama — ukoliko naiđete, možete rešiti po uzoru.
Pošto se iz greške na konzoli jasno vidi da je uzrok u vezi sa kodiranjem znakova, a sve upućuje na kodiranje ascii.
Zato sam u Vs Code uvezao JDK izvorni kod iz korenog direktorijuma, a zatim pretražio ceo direktorijum na sadržaj encoding ascii, da vidim ima li nečeg sumnjivog. Ispostavilo se da u fajlu koreni direktorijum JDK izvornog koda/make/common/SetupJavaCompilers.gmk na dva mesta stoji kodiranje vezano za ascii:
# make/common/SetupJavaCompilers.gmk
-encoding ascii \
...
-encoding ascii \Zato sam pokušao da oba -encoding ascii zamenim sa -encoding utf-8:
-encoding utf-8 \
...
-encoding utf-8 \Zatim ponovo izvršio make images za kompajliranje — kompajliranje je prošlo bez problema!
Time je posao završen!
Ovakav pristup kasnije čini čitanje, debagovanje i prilagođavanje JDK izvornog koda veoma zgodnim.
Referenca: https://segmentfault.com/a/1190000023251649
