Let’s build openjdk

As a builder, I am interested in how javanet builds their OpenJDK. In post https://blogs.oracle.com/kto/entry/jdk_build_musings, it provides some insights into the world of JDK build and test. I summarize those insights  which are generic and provides my comments and list below. They are some interested but very basic elements a build system should have,

  • Continuous Build/Integration & Automated Test
    Every component, every integration area, every junction point or merge point should be constantly built and smoke tested. Regarding to continuous build/integration, I strongly recommend book Continuous Integration: Improving Software Quality and Reducing Risk. Basically a build system should be well designed to support continuous build. There are lots of applications to support continuous build/integration, like Hudson, Jenkins, Cruise, etc. These applications have rich extensions to support your personal and special requirements.  For smoke testing, actually CI requires that in your build flows, ASA the complication of source code completes, a set of steps of testings should be executed as well.
  • Build and Test Machines/Multiple Platforms
    The hardware/machine resources for a build and test system is cheap, and a bargain if it keeps all developers shielded from bad changes, finding issues as early in the development process as possible. But it is also true that hardware/machine resources do not manage themselves, so there is also an expense to managing the systems, some of it can be automated but not everything. Virtual machines can provide benefits here, but they also introduce complications. Here, in my current working company, we make use of Oracle VM machines to provide virtual environments to support build/release activities. By adopting VMs, we can shorten our time in preparing environments and configuring environments. What is more, it can help make sure our configuration is consistent. In OpenJDK, they provide a script called /configure to help verify whether your environment is ready to build JDK or not. I will introduce it later.
  • Partial Builds/Build Flavors
    With the JDK we have a history of doing what we call partial builds. The hotspot team rarely builds the entire jdk, but instead just builds hotspot (because that is the only thing they changed) and then places their hotspot in a vetted jdk image that was built by the Release Engineering team at the last build promotion. Dito for the jdk teams that don’t work on hotspot, they rarely build hotspot. This was and still is considered a developer optimization, but is really only possible because of the way the JVM interfaces to the rest of the jdk, it rarely changes. To some degree, successful partial builds can indicate that the changes have not created an interface issue and can be considered somewhat ‘compatible’.
    These partial builds create issues when there are changes in both hotspot and the rest of the jdk, where both changes need to be integrated at the same time, or more likely, in a particular order, e.g. hotspot integrates a new extern interface, later the jdk team integrates a change that uses or requires that interface, ideally after the hotspot changes have been integrated into a promoted build so everyone’s partial builds have a chance of working.
    The partial builds came about mostly because of build time, but also because of the time and space needed to hold all the sources of parts of the product you never really needed. I also think there is a comfort effect by a developer not having to even see the sources to everything he or she doesn’t care about. I’m not convinced that the space and time of getting the sources is that significant anymore, although I’m sure I would get arguments on that. The build speed could also become less of an issue as the new build infrastructure speeds up building and makes incremental builds work properly. But stay tuned on this subject, partial builds are not going away, but it’s clear that life would be less complicated without them.
  • Mercurial
    Probably applies to Git or any distributed Source Code Management system too.
    OpenJDK just use Mercurial as its SCM tool. But here I am open that we can just choose on our demand. I have experience in Performance, VSS, SVN, and RCS! (Yes, The RCS from Linux! 🙂 )
  • Nested Repositories
    Not many projects have cut up the sources like the OpenJDK. There were multiple reasons for it, but it often creates issues for tools that either don’t understand the concept of nested repositories, or just cannot handle them.
  • Managing Build and Test Dependencies
    Some build and test dependencies are just packages or products installed on a system, I’ve often called those “system dependencies”. But many are just tarballs or zip bundles that needs to be placed somewhere and referred to. In my opinion, this is a mess, we need better organization here. Yeah yeah, I know someone will suggest Maven or Ivy, but it may not be that easy. Maven is an awesome tool to manage dependencies. You will definitely fall in love with him ASA you give him a hug!
  • Resolved Bugs and Changesets
    Having a quick connection between a resolved bug and the actual changes that fixed it is so extremely helpful that you cannot be without this. The connection needs to be both ways too. It may be possible to do this completely in the DSCM (Mercurial hooks), but in any case it is really critical to have that easy path between changes and bug reports. And if the build and test system has any kind of archival capability, also to that job data.
  • Distributed Builds
    Work in a distributed way is not so easy. I ever built software in a distributed way by using Cruise — it provides distributed support by running build in different agents in different machines. In testing, for example, for unit testing and smoke testing, we can try define two independent flows for unit testing and smoke testing respectively and triggered ASA the source code compilation succeeds.
  • Killing Builds and Tests
    At some point, you need to be able to kill off a build or test, probably many builds and many tests on many different systems. This can be easy on some systems, and hard with others. Using Virtual Machines or ghosting of disk images provides a chance of just system shutdowns and restarts with a pristine state, but that’s not simple logic to get right for all systems. I think here the concern is, how could we have a better control of our build flows. To have a better contorl the flows, we can add pauses, split the flows into some more independent flows and define their dependencies so one build can trigger another one only when all of the prerequiests are satisified.

To support and enhance the build of OpenJDK, OpenJDK team launched a project ‘https://blogs.oracle.com/kto/entry/build_infrastructure_project‘ to enhance in below points,

  • Different build flavors, same build flow
  • Ability to use ‘make -j N‘ on large multi-CPU machines is critical, as is being able to quickly and reliably get incremental builds done, this means:
    • target dependencies must be complete and accurate
    • nested makes should be avoided
    • ant scripts should be avoided for multiple reasons (it is a form of nested make), but we need to allow for IDE builds at the same time
    • rules that generate targets will need to avoid timestamp changes when the result has not changed
    • Java package compilations need to be made parallel and we also need to consider some kind of javac server setup (something that had been talked about a long time ago)
  • Continued use of different compilers: gcc/g++ (various versions), Sun Studio (various versions), and Windows Visual Studio (various versions)
  • Allow for clean cross compilation, this means making sure we just build it and not run it as part of the build
  • Nested repositories need to work well, so we need a way to share common make logic between repositories
  • The build dependencies should be managed as part of the makefiles

(I am going to study how to build software with MAKE. The build scritps of OpenJDK will be good materials. Hooray! 🙂 ). More details about OpenJDK build infrastructure group, http://openjdk.java.net/groups/build/

Let’s build OpenJDK

(Here I am building it based on http://hg.openjdk.java.net/jdk8/jdk8/raw-file/tip/README-builds.html – OpenJDK 8) and https://blogs.oracle.com/kto/entry/jdk8_new_build_infrastructure

Getting the source,

As I said above, OpenJDK uses http://mercurial.selenic.com/ to do its source control. So install Mercurial firstly if you have not yet done.

My Environment is

Linux luhuang-VirtualBox 3.0.0-32-generic-pae #51-Ubuntu SMP Thu Mar 21 16:09:48 UTC 2013 i686 i686 i386 GNU/Linux

Run below commands as ‘root’ user or sudo,

Install and update your aptitude, purge openjdk-6* if installed, and install necessary packages,

1. apt-get install aptitude

root@luhuang-VirtualBox:/home/luhuang# apt-get install aptitude
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
  libboost-iostreams1.46.1 libclass-accessor-perl libcwidget3 libept1
  libio-string-perl libparse-debianchangelog-perl libsub-name-perl
Suggested packages:
  aptitude-doc-en aptitude-doc tasksel debtags libcwidget-dev
  libhtml-parser-perl libhtml-template-perl libxml-simple-perl
The following NEW packages will be installed:
  aptitude libboost-iostreams1.46.1 libclass-accessor-perl libcwidget3 libept1
  libio-string-perl libparse-debianchangelog-perl libsub-name-perl
0 upgraded, 8 newly installed, 0 to remove and 449 not upgraded.
Need to get 2,985 kB of archives.
After this operation, 9,236 kB of additional disk space will be used.
Do you want to continue [Y/n]? Y
...

2. aptitude update

3. apt-get purge openjdk-6*

Why we have to purge openjdk-6* before continue?

“Install a bootstrap JDK. All OpenJDK builds require access to a previously released JDK called the bootstrap JDK or boot JDK. The general rule is that the bootstrap JDK must be an instance of the previous major release of the JDK. In addition, there may be a requirement to use a release at or beyond a particular update level”

4. aptitude install mercurial openjdk-7-jdk rpm ssh expect tcsh csh ksh gawk g++ ccache build-essential lesstif2-dev

root@luhuang-VirtualBox:/home/luhuang# aptitude install mercurial openjdk-7-jdk rpm ssh expect tcsh csh ksh gawk g++ ccache build-essential lesstif2-dev
The following NEW packages will be installed:
  ca-certificates-java{a} ccache csh expect gawk icedtea-7-jre-jamvm{a}
  java-common{a} ksh lesstif2{a} lesstif2-dev libbonobo2-0{a}
  libbonobo2-common{a} libexpat1-dev{a} libfontconfig1-dev{a}
  libfreetype6-dev{a} libgnome2-0{a} libice-dev{a} libnss3-1d{a}
  libpthread-stubs0{a} libpthread-stubs0-dev{a} librpm2{a} librpmbuild2{a}
  librpmio2{a} librpmsign0{a} libsigsegv2{a} libsm-dev{a} libx11-dev{a}
  libxau-dev{a} libxcb1-dev{a} libxdmcp-dev{a} libxext-dev{a} libxft-dev{a}
  libxp-dev{a} libxrender-dev{a} libxt-dev{a} mercurial mercurial-common{a}
  openjdk-7-jdk openjdk-7-jre{a} openjdk-7-jre-headless{a}
  openjdk-7-jre-lib{a} openssh-server{a} rpm rpm-common{a} rpm2cpio{a} ssh
  ssh-import-id{a} tcl8.5{a} tcsh ttf-dejavu-extra{a} tzdata-java{a}
  x11proto-core-dev{a} x11proto-input-dev{a} x11proto-kb-dev{a}
  x11proto-print-dev{a} x11proto-render-dev{a} x11proto-xext-dev{a}
  xorg-sgml-doctools{a} xtrans-dev{a} zlib1g-dev{a}
The following packages will be upgraded:
  libexpat1 libfreetype6 libnss3 tzdata
4 packages upgraded, 60 newly installed, 0 to remove and 445 not upgraded.
Need to get 80.5 MB/81.4 MB of archives. After unpacking 148 MB will be used.
Do you want to continue? [Y/n/?]
...

(It seems the dependencies have changed since their last update in the pages, I still have to install below packages):

5. apt-get install libX11-dev libxext-dev libxrender-dev libxtst-dev

6. apt-get install libcups2-dev

7. apt-get install libasound2-dev

Run below commands as your working user to get the jdk8/build sources. Here for convenient, I am also using root user:

8. hg clone http://hg.openjdk.java.net/jdk8/build jdk8-build

9. cd jdk8-build

10. sh ./get_source.sh

Example:

root@luhuang-VirtualBox:/media/sf_shared# hg clone http://hg.openjdk.java.net/jdk8/build jdk8-build
requesting all changes
adding changesets
adding manifests
adding file changes
added 774 changesets with 1018 changes to 118 files
updating to branch default
101 files updated, 0 files merged, 0 files removed, 0 files unresolved
...
root@luhuang-VirtualBox:/media/sf_shared/jdk8-build# sh ./get_source.sh
# Repositories:  corba jaxp jaxws langtools jdk hotspot nashorn

                corba:   /usr/bin/python -u /usr/bin/hg clone http://hg.openjdk.java.net/jdk8/build/corba corba
                 jaxp:   /usr/bin/python -u /usr/bin/hg clone http://hg.openjdk.java.net/jdk8/build/jaxp jaxp
Waiting 5 secs before spawning next background command.
                 jaxp:   requesting all changes
                corba:   requesting all changes
                corba:   adding changesets
                 jaxp:   adding changesets
                jaxws:   /usr/bin/python -u /usr/bin/hg clone http://hg.openjdk.java.net/jdk8/build/jaxws jaxws
            langtools:   /usr/bin/python -u /usr/bin/hg clone http://hg.openjdk.java.net/jdk8/build/langtools langtools
...

Then do your build:

11. chmod a+x common/bin/*

12. cd common/makefiles

13. bash ../autoconf/configure

Configure will try to figure out what system you are running on and where all necessary build components are. If you have all prerequisites for building installed, it should find everything. If it fails to detect any component automatically, it will exit and inform you about the problem. I think the philosophy of Configure is very awesome. In my daily builds, I have some scripts and docs to do sanity of a build server but I don’t design a tool elegant like this to automate everything and detect missing components and give smart suggestions like this!
Example (a failed configure check with suggestion):

configure: error: Could not find all X11 headers (shape.h Xrender.h XTest.h). You might be able to fix this by running 'sudo apt-get install libX11-dev libxext-dev libxrender-dev libxtst-dev'.
configure exiting with result code 1
configure: error: Could not find cups! You might be able to fix this by running 'sudo apt-get install libcups2-dev'.
configure exiting with result code 1

Example (a successful configure check):

...
A new configuration has been successfully created in
/media/sf_shared/jdk8-build/build/linux-x86-normal-server-release
using default settings.

Configuration summary:
* Debug level:    release
* JDK variant:    normal
* JVM variants:   server
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 32

Tools summary:
* Boot JDK:       java version "1.7.0_21" OpenJDK Runtime Environment (IcedTea 2.3.9) (7u21-2.3.9-0ubuntu0.11.10.1) OpenJDK Client VM (build 23.7-b01, mixed mode, sharing)  (at /usr/lib/jvm/java-7-openjdk)
* C Compiler:     gcc-4.6 (Ubuntu/Linaro 4.6.1-9ubuntu3) version 4.6.1 (at /usr/bin/gcc-4.6)
* C++ Compiler:   g++-4.6 (Ubuntu/Linaro 4.6.1-9ubuntu3) version 4.6.1 (at /usr/bin/g++-4.6)

Build performance summary:
* Cores to use:   1
* Memory limit:   4031 MB
* ccache status:  installed and in use
...

Wow, so elegant! A good philosophy to do sanity testing for a build server!

Building JDK 8 requires use of a version of JDK 7 that is at Update 7 or newer. JDK 8 developers should not use JDK 8 as the boot JDK, to ensure that JDK 8 dependencies are not introduced into the parts of the system that are built with JDK 7

Note that some Linux systems have a habit of pre-populating your environment variables for you, for example JAVA_HOME might get pre-defined for you to refer to the JDK installed on your Linux system. You will need to unset JAVA_HOME. It’s a good idea to run env and verify the environment variables you are getting from the default system settings make sense for building the OpenJDK.

14. make
Ready? Fasten your seatbelt. Go!

...
Compiling /media/sf_shared/jdk8-build/hotspot/src/share/vm/utilities/yieldingWorkgroup.cpp
Compiling /media/sf_shared/jdk8-build/hotspot/src/share/vm/runtime/vm_version.cpp
Linking vm...
ln: creating symbolic link `libjvm.so.1': Protocol error
Making signal interposition lib...
Making SA debugger back-end...
**NOTICE** Dtrace support disabled: /usr/include/sys/sdt.h not found
All done.
INFO: ENABLE_FULL_DEBUG_SYMBOLS=1
INFO: ALT_OBJCOPY=/usr/bin/objcopy
INFO: /usr/bin/objcopy cmd found so will create .debuginfo files.
INFO: STRIP_POLICY=min_strip
INFO: ZIP_DEBUGINFO_FILES=1
warning: [options] bootstrap class path not set in conjunction with -source 1.6
1 warning
Generating linux_i486_docs/jvmti.html
INFO: ENABLE_FULL_DEBUG_SYMBOLS=1
INFO: ALT_OBJCOPY=/usr/bin/objcopy
INFO: /usr/bin/objcopy cmd found so will create .debuginfo files.
INFO: STRIP_POLICY=min_strip
INFO: ZIP_DEBUGINFO_FILES=1
## Finished hotspot (build time 00:07:09)

## Starting corba
Compiling 6 files for BUILD_LOGUTIL
Creating corba/btjars/logutil.jar
Compiling 141 files for BUILD_IDLJ
...
## Finished jdk (build time 00:11:38)

----- Build times -------
Start 2013-08-28 18:54:01
End   2013-08-28 19:40:17
00:00:28 corba
00:31:07 hotspot
00:00:32 jaxp
00:01:57 jaxws
00:11:38 jdk
00:00:32 langtools
00:46:16 TOTAL
-------------------------
Finished building OpenJDK for target 'default'

Look, it has my signature!

luhuang@luhuang:~/build/jdk8-build/build/linux-x86-normal-server-release/jdk/bin$ date
Wed Aug 28 20:12:00 CST 2013
luhuang@luhuang:~/build/jdk8-build/build/linux-x86-normal-server-release/jdk/bin$ ./java -version
openjdk version "1.8.0-internal"
OpenJDK Runtime Environment (build 1.8.0-internal-luhuang_2013_08_28_18_53-b00)
OpenJDK Server VM (build 25.0-b47, mixed mode)
luhuang@luhuang:~/build/jdk8-build/build/linux-x86-normal-server-release/jdk/bin$

So, we have done with the build of OpenJDK8. So easy! Thanks to the elegant build infrastructure of OpenJDK that we can build OpenJDK in just several commands!