Skip to content

Commit c7652eb

Browse files
authored
[7.x][ML] Add cross compilation support, Docker images and CI for aarch64 (#1138)
Following on from #1134, this change adds: 1. The ability to cross compile a Linux build for aarch64 on Linux on another hardware architecture 2. A Docker container for x86_64 that is set up for cross compiling for aarch64 3. A Docker container for aarch64 that can both build and run unit tests 4. Changes to CI scripts to utilise these Docker containers where appropriate Backport of #1135
1 parent 8877543 commit c7652eb

21 files changed

+720
-58
lines changed

3rd_party/3rd_party.sh

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,25 @@ case `uname` in
7575
STL_LOCATION=
7676
ZLIB_LOCATION=
7777
else
78-
echo "Cannot cross compile to $CPP_CROSS_COMPILE"
79-
exit 3
78+
SYSROOT=/usr/local/sysroot-$CPP_CROSS_COMPILE-linux-gnu
79+
BOOST_LOCATION=$SYSROOT/usr/local/gcc75/lib
80+
BOOST_COMPILER=gcc
81+
if [ "$CPP_CROSS_COMPILE" = aarch64 ] ; then
82+
BOOST_ARCH=a64
83+
else
84+
echo "Cannot cross compile to $CPP_CROSS_COMPILE"
85+
exit 3
86+
fi
87+
BOOST_EXTENSION=mt-${BOOST_ARCH}-1_71.so.1.71.0
88+
BOOST_LIBRARIES='atomic chrono date_time filesystem iostreams log log_setup program_options regex system thread'
89+
XML_LOCATION=$SYSROOT/usr/local/gcc75/lib
90+
XML_EXTENSION=.so.2
91+
GCC_RT_LOCATION=$SYSROOT/usr/local/gcc75/lib64
92+
GCC_RT_EXTENSION=.so.1
93+
STL_LOCATION=$SYSROOT/usr/local/gcc75/lib64
94+
STL_PREFIX=libstdc++
95+
STL_EXTENSION=.so.6
96+
ZLIB_LOCATION=
8097
fi
8198
;;
8299

@@ -183,7 +200,7 @@ fi
183200
case `uname` in
184201

185202
Linux)
186-
if [ -n "$INSTALL_DIR" -a -z "$CPP_CROSS_COMPILE" ] ; then
203+
if [ -n "$INSTALL_DIR" -a "$CPP_CROSS_COMPILE" != macosx ] ; then
187204
cd "$INSTALL_DIR"
188205
for FILE in `find . -type f | egrep -v '^core|-debug$|libMl'`
189206
do
@@ -192,13 +209,7 @@ case `uname` in
192209
if [ $? -eq 0 ] ; then
193210
echo "Set RPATH in $FILE"
194211
else
195-
# Set RPATH for 3rd party libraries that reference other libraries we ship
196-
ldd $FILE | grep /usr/local/lib >/dev/null 2>&1 && patchelf --set-rpath '$ORIGIN/.' $FILE
197-
if [ $? -eq 0 ] ; then
198-
echo "Set RPATH in $FILE"
199-
else
200-
echo "Did not set RPATH in $FILE"
201-
fi
212+
echo "Did not set RPATH in $FILE"
202213
fi
203214
done
204215
fi

build-setup/linux.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,7 @@ export CPP_SRC_HOME=$HOME/ml-cpp
2727
You need the C++ compiler and the headers for the `zlib` library that comes with the OS. You also need the archive utilities `unzip` and `bzip2`. Finally, the unit tests for date/time parsing require the `tzdata` package that contains the Linux timezone database. On RHEL/CentOS these can be installed using:
2828

2929
```
30-
sudo yum install bzip2
31-
sudo yum install gcc-c++
32-
sudo yum install texinfo
33-
sudo yum install tzdata
34-
sudo yum install unzip
35-
sudo yum install zlib-devel
30+
sudo yum install bzip2 gcc-c++ texinfo tzdata unzip zlib-devel
3631
```
3732

3833
On other Linux distributions the package names are generally the same and you just need to use the correct package manager to install these packages.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Machine Learning Build Machine Setup for Linux aarch64 cross compiled
2+
3+
You will need the following environment variables to be defined:
4+
5+
- `JAVA_HOME` - Should point to the JDK you want to use to run Gradle.
6+
- `CPP_CROSS_COMPILE` - Should be set to "aarch64".
7+
- `CPP_SRC_HOME` - Only required if building the C++ code directly using `make`, as Gradle sets it automatically.
8+
- `PATH` - Must have `/usr/local/gcc75/bin` before `/usr/bin` and `/bin`.
9+
- `LD_LIBRARY_PATH` - Must have `/usr/local/gcc75/lib64` and `/usr/local/gcc75/lib` before `/usr/lib` and `/lib`.
10+
11+
For example, you might create a .bashrc file in your home directory containing this:
12+
13+
```
14+
umask 0002
15+
export JAVA_HOME=/usr/local/jdk1.8.0_121
16+
export LD_LIBRARY_PATH=/usr/local/gcc75/lib64:/usr/local/gcc75/lib:/usr/lib:/lib
17+
export PATH=$JAVA_HOME/bin:/usr/local/gcc75/bin:/usr/bin:/bin:/usr/sbin:/sbin
18+
# Only required if building the C++ code directly using make - adjust depending on the location of your Git clone
19+
export CPP_SRC_HOME=$HOME/ml-cpp
20+
export CPP_CROSS_COMPILE=aarch64
21+
```
22+
23+
### Initial Preparation
24+
25+
Start by configuring a native Linux aarch64 build server as described in [linux.md](linux.md).
26+
27+
The remainder of these instructions assume the native aarch64 build server you have configured is for CentOS 7. This is what builds for distribution are currently built on.
28+
29+
On the fully configured native aarch64 build server, run the following commands:
30+
31+
```
32+
cd /usr
33+
tar jcvf ~/usr-aarch64-linux-gnu.tar.bz2 include lib lib64 local
34+
```
35+
36+
These instructions assume the host platform is also CentOS 7, but x86_64 instead of aarch64. It makes life much easier if the host distribution is the same as the target distribution.
37+
38+
Transfer the archive created in your home directory on the native aarch64 build server, `usr-aarch64-linux-gnu.tar.bz2`, to your home directory on the x86_64 host build server.
39+
40+
### OS Packages
41+
42+
You need the C++ compiler and the headers for the `zlib` library that comes with the OS. You also need the archive utilities `unzip` and `bzip2`. On RHEL/CentOS these can be installed using:
43+
44+
```
45+
sudo yum install bzip2 gcc-c++ texinfo unzip zlib-devel
46+
```
47+
48+
### Transferred Build Dependencies
49+
50+
Add the dependencies that you copied from the fully configured native aarch64 build server in the "Initial Preparation" step.
51+
52+
```
53+
sudo mkdir -p /usr/local/sysroot-aarch64-linux-gnu/usr
54+
cd /usr/local/sysroot-aarch64-linux-gnu/usr
55+
sudo tar jxvf ~/usr-aarch64-linux-gnu.tar.bz2
56+
cd ..
57+
sudo ln -s usr/lib lib
58+
sudo ln -s usr/lib64 lib64
59+
```
60+
61+
### General settings for building the tools
62+
63+
Most of the tools are built via a GNU "configure" script. There are some environment variables that affect the behaviour of this. Therefore, when building ANY tool on Linux, set the following environment variables:
64+
65+
```
66+
export CFLAGS='-g -O3 -fstack-protector -D_FORTIFY_SOURCE=2'
67+
export CXXFLAGS='-g -O3 -fstack-protector -D_FORTIFY_SOURCE=2'
68+
export LDFLAGS='-Wl,-z,relro -Wl,-z,now'
69+
export LDFLAGS_FOR_TARGET='-Wl,-z,relro -Wl,-z,now'
70+
unset LIBRARY_PATH
71+
```
72+
73+
These environment variables only need to be set when building tools on Linux. They should NOT be set when compiling the Machine Learning source code (as this should pick up all settings from our Makefiles).
74+
75+
### binutils (bootstrap version)
76+
77+
Since we build with a more recent gcc than comes with the host system, we must build it from source. To build a cross compiler we need cross build tools, so we need to build versions that are compatible with the system compiler that we'll use to build the more recent gcc.
78+
79+
Download `binutils-2.25.tar.bz2` from <http://ftpmirror.gnu.org/binutils/binutils-2.25.tar.bz2>.
80+
81+
Uncompress and untar the resulting file. Then run:
82+
83+
```
84+
unset LD_LIBRARY_PATH
85+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
86+
./configure --with-sysroot=/usr/local/sysroot-aarch64-linux-gnu --target=aarch64-linux-gnu --with-system-zlib --disable-multilib --disable-libstdcxx
87+
```
88+
89+
This should build an appropriate Makefile. Assuming it does, type:
90+
91+
```
92+
make
93+
sudo make install
94+
```
95+
96+
to install.
97+
98+
### gcc
99+
100+
We have to build on old Linux versions to enable our software to run on the older versions of Linux that users have. However, this means the default compiler on our Linux build servers is also very old. To enable use of more modern C++ features, we use the default compiler to build a newer version of gcc and then use that to build all our other dependencies.
101+
102+
Download `gcc-7.5.0.tar.gz` from <http://ftpmirror.gnu.org/gcc/gcc-7.5.0/gcc-7.5.0.tar.gz>.
103+
104+
Unlike most automake-based tools, gcc must be built in a directory adjacent to the directory containing its source code, so build and install it like this:
105+
106+
```
107+
tar zxvf gcc-7.5.0.tar.gz
108+
cd gcc-7.5.0
109+
contrib/download_prerequisites
110+
sed -i -e 's/$(SHLIB_LDFLAGS)/-Wl,-z,relro -Wl,-z,now $(SHLIB_LDFLAGS)/' libgcc/config/t-slibgcc
111+
cd ..
112+
mkdir gcc-7.5.0-build
113+
cd gcc-7.5.0-build
114+
unset LD_LIBRARY_PATH
115+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
116+
../gcc-7.5.0/configure --prefix=/usr/local/gcc75 --with-sysroot=/usr/local/sysroot-aarch64-linux-gnu --target=aarch64-linux-gnu --enable-languages=c,c++ --enable-vtable-verify --with-system-zlib --disable-multilib
117+
make -j 6
118+
sudo env PATH="$PATH" make install
119+
```
120+
121+
(Note the `env PATH="$PATH"` bit in the install command - this is because the cross tools we put in `/usr/local/bin` are needed during the install.)
122+
123+
To confirm that everything works correctly run:
124+
125+
```
126+
aarch64-linux-gnu-g++ --version
127+
```
128+
129+
It should print:
130+
131+
```
132+
aarch64-linux-gnu-g++ (GCC) 7.5.0
133+
```
134+
135+
in the first line of the output. If it doesn't then double check that `/usr/local/gcc75/bin` is near the beginning of your `PATH`.
136+
137+
### binutils (final version)
138+
139+
Also due to building on old Linux versions yet wanting to use modern libraries we have to install an up-to-date version of binutils. This will be used in preference to the bootstrap version by ensuring that `/usr/local/gcc75/bin` is at the beginning of `PATH`.
140+
141+
Download `binutils-2.34.tar.bz2` from <http://ftpmirror.gnu.org/binutils/binutils-2.34.tar.bz2>.
142+
143+
Uncompress and untar the resulting file. Then run:
144+
145+
```
146+
./configure --prefix=/usr/local/gcc75 --with-sysroot=/usr/local/sysroot-aarch64-linux-gnu --target=aarch64-linux-gnu --enable-vtable-verify --with-system-zlib --disable-multilib --disable-libstdcxx --with-gcc-major-version-only
147+
```
148+
149+
This should build an appropriate Makefile. Assuming it does, type:
150+
151+
```
152+
make
153+
sudo make install
154+
```
155+
156+
to install.
157+
158+
### patchelf
159+
160+
Obtain patchelf from <http://nixos.org/releases/patchelf/patchelf-0.9/> - the download file will be `patchelf-0.9.tar.bz2`.
161+
162+
Extract it to a temporary directory using:
163+
164+
```
165+
bzip2 -cd patchelf-0.9.tar.bz2 | tar xvf -
166+
```
167+
168+
In the resulting `patchelf-0.9` directory, run the:
169+
170+
```
171+
./configure --prefix=/usr/local/gcc75
172+
```
173+
174+
script. This should build an appropriate Makefile. Assuming it does, run:
175+
176+
```
177+
make
178+
sudo make install
179+
```
180+
181+
to complete the build.

build.gradle

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,29 @@ if (cppCrossCompile == null) {
3333
cppCrossCompile = ''
3434
}
3535
}
36-
if (cppCrossCompile != '' && cppCrossCompile != 'macosx') {
37-
throw new GradleException("CPP_CROSS_COMPILE property must be empty or 'macosx'")
36+
if (cppCrossCompile != '' && cppCrossCompile != 'macosx' && cppCrossCompile != 'aarch64') {
37+
throw new GradleException("CPP_CROSS_COMPILE property must be empty, 'macosx' or 'aarch64'")
38+
}
39+
40+
String artifactClassifier;
41+
if (isWindows) {
42+
artifactClassifier = 'windows-x86_64'
43+
} else if (isMacOsX || cppCrossCompile == 'macosx') {
44+
artifactClassifier = 'darwin-x86_64'
45+
} else if (cppCrossCompile != '') {
46+
artifactClassifier = 'linux-' + cppCrossCompile
47+
} else {
48+
String osArch = System.properties['os.arch']
49+
// Some versions of Java report hardware architectures that
50+
// don't match other tools - these need to be normalized
51+
if (osArch == 'amd64') {
52+
osArch = 'x86_64'
53+
} else if (osArch == 'i386') {
54+
osArch = 'x86'
55+
}
56+
artifactClassifier = 'linux-' + osArch
3857
}
3958

40-
String artifactClassifier = (isWindows ? "windows-x86_64" : ((isMacOsX || cppCrossCompile == 'macosx') ? "darwin-x86_64" : "linux-x86_64"))
41-
4259
// Always do the C++ build using bash (Git bash on Windows)
4360
project.ext.bash = isWindows ? "C:\\Program Files\\Git\\bin\\bash" : "/bin/bash"
4461
project.ext.make = (isMacOsX || isWindows) ? "gnumake" : (isLinux ? "make" : "gmake")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
#
3+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
4+
# or more contributor license agreements. Licensed under the Elastic License;
5+
# you may not use this file except in compliance with the Elastic License.
6+
#
7+
8+
# Builds the Docker image that can be used to compile the machine learning
9+
# C++ code for Linux.
10+
#
11+
# This script is not intended to be run regularly. When changing the tools
12+
# or 3rd party components required to build the machine learning C++ code
13+
# increment the version, change the Dockerfile and build a new image to be
14+
# used for subsequent builds on this branch. Then update the version to be
15+
# used for builds in docker/linux_builder/Dockerfile.
16+
17+
HOST=push.docker.elastic.co
18+
ACCOUNT=ml-dev
19+
REPOSITORY=ml-linux-aarch64-cross-build
20+
VERSION=1
21+
22+
set -e
23+
24+
cd `dirname $0`
25+
26+
docker build --no-cache -t $HOST/$ACCOUNT/$REPOSITORY:$VERSION linux_aarch64_cross_image
27+
# Get a username and password for this by visiting
28+
# https://docker.elastic.co:7000 and allowing it to authenticate against your
29+
# GitHub account
30+
docker login $HOST
31+
docker push $HOST/$ACCOUNT/$REPOSITORY:$VERSION
32+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
#
3+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
4+
# or more contributor license agreements. Licensed under the Elastic License;
5+
# you may not use this file except in compliance with the Elastic License.
6+
#
7+
8+
# Builds the Docker image that can be used to compile the machine learning
9+
# C++ code for Linux.
10+
#
11+
# This script is not intended to be run regularly. When changing the tools
12+
# or 3rd party components required to build the machine learning C++ code
13+
# increment the version, change the Dockerfile and build a new image to be
14+
# used for subsequent builds on this branch. Then update the version to be
15+
# used for builds in docker/linux_builder/Dockerfile.
16+
17+
if [ `uname -m` != aarch64 ] ; then
18+
echo "Native build images must be built on the correct hardware architecture"
19+
echo "Required: aarch64, Current:" `uname -m`
20+
exit 1
21+
fi
22+
23+
HOST=push.docker.elastic.co
24+
ACCOUNT=ml-dev
25+
REPOSITORY=ml-linux-aarch64-native-build
26+
VERSION=1
27+
28+
set -e
29+
30+
cd `dirname $0`
31+
32+
docker build --no-cache -t $HOST/$ACCOUNT/$REPOSITORY:$VERSION linux_aarch64_native_image
33+
# Get a username and password for this by visiting
34+
# https://docker.elastic.co:7000 and allowing it to authenticate against your
35+
# GitHub account
36+
docker login $HOST
37+
docker push $HOST/$ACCOUNT/$REPOSITORY:$VERSION
38+

dev-tools/docker/build_linux_build_image.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
# used for subsequent builds on this branch. Then update the version to be
1515
# used for builds in docker/linux_builder/Dockerfile.
1616

17+
if [ `uname -m` != x86_64 ] ; then
18+
echo "Native build images must be built on the correct hardware architecture"
19+
echo "Required: x86_64, Current:" `uname -m`
20+
exit 1
21+
fi
22+
1723
HOST=push.docker.elastic.co
1824
ACCOUNT=ml-dev
1925
REPOSITORY=ml-linux-build
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
# or more contributor license agreements. Licensed under the Elastic License;
4+
# you may not use this file except in compliance with the Elastic License.
5+
#
6+
7+
# Increment the version here when a new tools/3rd party components image is built
8+
FROM docker.elastic.co/ml-dev/ml-linux-aarch64-cross-build:1
9+
10+
MAINTAINER David Roberts <[email protected]>
11+
12+
# Copy the current Git repository into the container
13+
COPY . /ml-cpp/
14+
15+
# Tell the build we want to cross compile
16+
ENV CPP_CROSS_COMPILE aarch64
17+
18+
# Pass through any version qualifier (default none)
19+
ARG VERSION_QUALIFIER=
20+
21+
# Pass through whether this is a snapshot build (default yes if not specified)
22+
ARG SNAPSHOT=yes
23+
24+
# Run the build
25+
RUN \
26+
/ml-cpp/dev-tools/docker/docker_entrypoint.sh
27+

0 commit comments

Comments
 (0)