1#!/bin/bash
2#
3# Execute this tool to setup the environment and build binary releases
4# for Percona-Server starting from a fresh tree.
5#
6# Usage: build-binary.sh [target dir]
7# The default target directory is the current directory. If it is not
8# supplied and the current directory is not empty, it will issue an error in
9# order to avoid polluting the current directory after a test run.
10#
11
12# Bail out on errors, be strict
13set -ue
14
15# Examine parameters
16TARGET="$(uname -m)"
17TARGET_CFLAGS=''
18QUIET='VERBOSE=1'
19WITH_JEMALLOC=''
20WITH_MECAB_OPTION=''
21DEBUG_EXTRA=''
22WITH_SSL='/usr'
23WITH_SSL_TYPE='system'
24OPENSSL_INCLUDE=''
25OPENSSL_LIBRARY=''
26CRYPTO_LIBRARY=''
27TAG=''
28#
29CMAKE_BUILD_TYPE=''
30COMMON_FLAGS=''
31#
32TOKUDB_BACKUP_VERSION=''
33# enable asan
34ENABLE_ASAN=0
35#
36# Some programs that may be overriden
37TAR=${TAR:-tar}
38
39# Check if we have a functional getopt(1)
40if ! getopt --test
41then
42    go_out="$(getopt --options=iqdvj:m:t: \
43        --longoptions=i686,quiet,debug,valgrind,with-jemalloc:,with-mecab:,with-yassl,with-ssl:,tag: \
44        --name="$(basename "$0")" -- "$@")"
45    test $? -eq 0 || exit 1
46    eval set -- $go_out
47fi
48
49for arg
50do
51    case "$arg" in
52    -- ) shift; break;;
53    -i | --i686 )
54        shift
55        TARGET="i686"
56        TARGET_CFLAGS="-m32 -march=i686"
57        ;;
58    -d | --debug )
59        shift
60        CMAKE_BUILD_TYPE='Debug'
61        BUILD_COMMENT="${BUILD_COMMENT:-}-debug"
62        TARBALL_SUFFIX="-debug"
63        DEBUG_EXTRA="-DDEBUG_EXTNAME=OFF -DWITH_DEBUG=ON"
64        ;;
65    -a | --asan )
66        shift
67        ENABLE_ASAN=1
68        ;;
69    -v | --valgrind )
70        shift
71        CMAKE_OPTS="${CMAKE_OPTS:-} -DWITH_VALGRIND=ON"
72        BUILD_COMMENT="${BUILD_COMMENT:-}-valgrind"
73        ;;
74    -q | --quiet )
75        shift
76        QUIET=''
77        ;;
78    -j | --with-jemalloc )
79        shift
80        WITH_JEMALLOC="$1"
81        shift
82        ;;
83    -m | --with-mecab )
84        shift
85        WITH_MECAB_OPTION="-DWITH_MECAB=$1"
86        shift
87        ;;
88    --with-yassl )
89        shift
90        WITH_SSL_TYPE="bundled"
91        ;;
92    --with-ssl )
93        shift
94        WITH_SSL="$1"
95        shift
96        # Set openssl and crypto library path
97        if test -e "$WITH_SSL/lib/libssl.a"
98        then
99            OPENSSL_INCLUDE="-DOPENSSL_INCLUDE_DIR=$WITH_SSL/include"
100            OPENSSL_LIBRARY="-DOPENSSL_LIBRARY=$WITH_SSL/lib/libssl.a"
101            CRYPTO_LIBRARY="-DCRYPTO_LIBRARY=$WITH_SSL/lib/libcrypto.a"
102        elif test -e "$WITH_SSL/lib64/libssl.a"
103        then
104            OPENSSL_INCLUDE="-DOPENSSL_INCLUDE_DIR=$WITH_SSL/include"
105            OPENSSL_LIBRARY="-DOPENSSL_LIBRARY=$WITH_SSL/lib64/libssl.a"
106            CRYPTO_LIBRARY="-DCRYPTO_LIBRARY=$WITH_SSL/lib64/libcrypto.a"
107        else
108            echo >&2 "Cannot find libssl.a in $WITH_SSL"
109            exit 3
110        fi
111        ;;
112    -t | --tag )
113        shift
114        TAG="$1"
115        shift
116        ;;
117    esac
118done
119
120# Working directory
121if test "$#" -eq 0
122then
123    WORKDIR="$(pwd)"
124
125    # Check that the current directory is not empty
126    if test "x$(echo *)" != "x*"
127    then
128        echo >&2 \
129            "Current directory is not empty. Use $0 . to force build in ."
130        exit 1
131    fi
132elif test "$#" -eq 1
133then
134    WORKDIR="$1"
135
136    # Check that the provided directory exists and is a directory
137    if ! test -d "$WORKDIR"
138    then
139        echo >&2 "$WORKDIR is not a directory"
140        exit 1
141    fi
142else
143    echo >&2 "Usage: $0 [target dir]"
144    exit 1
145fi
146
147WORKDIR_ABS="$(cd "$WORKDIR"; pwd)"
148
149SOURCEDIR="$(cd $(dirname "$0"); cd ..; pwd)"
150test -e "$SOURCEDIR/MYSQL_VERSION" || exit 2
151
152# The number of processors is a good default for -j
153if test -e "/proc/cpuinfo"
154then
155    PROCESSORS="$(grep -c ^processor /proc/cpuinfo)"
156else
157    PROCESSORS=4
158fi
159
160# Extract version from the VERSION file
161source "$SOURCEDIR/MYSQL_VERSION"
162MYSQL_VERSION="$MYSQL_VERSION_MAJOR.$MYSQL_VERSION_MINOR.$MYSQL_VERSION_PATCH"
163PERCONA_SERVER_VERSION="$(echo $MYSQL_VERSION_EXTRA | sed 's/^-//')"
164PRODUCT="Percona-Server-$MYSQL_VERSION-$PERCONA_SERVER_VERSION"
165TOKUDB_BACKUP_VERSION="${MYSQL_VERSION}${MYSQL_VERSION_EXTRA}"
166
167# Build information
168if test -e "$SOURCEDIR/Docs/INFO_SRC"
169then
170    REVISION="$(cd "$SOURCEDIR"; grep '^short: ' Docs/INFO_SRC |sed -e 's/short: //')"
171elif [ -n "$(which git)" -a -d "$SOURCEDIR/.git" ];
172then
173    REVISION="$(git rev-parse --short HEAD)"
174else
175    REVISION=""
176fi
177PRODUCT_FULL="Percona-Server-$MYSQL_VERSION-$PERCONA_SERVER_VERSION"
178PRODUCT_FULL="$PRODUCT_FULL-$TAG$(uname -s)${DIST_NAME:-}.$TARGET${GLIBC_VER:-}${TARBALL_SUFFIX:-}"
179COMMENT="Percona Server (GPL), Release ${MYSQL_VERSION_EXTRA#-}"
180COMMENT="$COMMENT, Revision $REVISION${BUILD_COMMENT:-}"
181
182# Compilation flags
183export CC=${CC:-gcc}
184export CXX=${CXX:-g++}
185
186# If gcc >= 4.8 we can use ASAN in debug build but not if valgrind build also
187if [[ $ENABLE_ASAN -eq 1 ]]; then
188    if [[ "$CMAKE_BUILD_TYPE" == "Debug" ]] && [[ "${CMAKE_OPTS:-}" != *WITH_VALGRIND=ON* ]]; then
189        GCC_VERSION=$(${CC} -dumpversion)
190        GT_VERSION=$(echo -e "4.8.0\n${GCC_VERSION}" | sort -t. -k1,1nr -k2,2nr -k3,3nr | head -1)
191        if [ "${GT_VERSION}" = "${GCC_VERSION}" ]; then
192            DEBUG_EXTRA="${DEBUG_EXTRA} -DWITH_ASAN=ON"
193        fi
194    fi
195fi
196
197# TokuDB cmake flags
198if test -d "$SOURCEDIR/storage/tokudb"
199then
200    CMAKE_OPTS="${CMAKE_OPTS:-} -DBUILD_TESTING=OFF -DUSE_GTAGS=OFF -DUSE_CTAGS=OFF -DUSE_ETAGS=OFF -DUSE_CSCOPE=OFF -DTOKUDB_BACKUP_PLUGIN_VERSION=${TOKUDB_BACKUP_VERSION}"
201
202    if test "x$CMAKE_BUILD_TYPE" != "xDebug"
203    then
204        CMAKE_OPTS="${CMAKE_OPTS:-} -DTOKU_DEBUG_PARANOID=OFF"
205    else
206        CMAKE_OPTS="${CMAKE_OPTS:-} -DTOKU_DEBUG_PARANOID=ON"
207    fi
208
209    if [[ $CMAKE_OPTS == *WITH_VALGRIND=ON* ]]
210    then
211        CMAKE_OPTS="${CMAKE_OPTS:-} -DUSE_VALGRIND=ON"
212    fi
213fi
214
215#
216# Attempt to remove any optimisation flags from the debug build
217# BLD-238 - bug1408232
218if [ -n "$(which rpm)" ]; then
219  export COMMON_FLAGS=$(rpm --eval %optflags | sed -e "s|march=i386|march=i686|g")
220  if test "x$CMAKE_BUILD_TYPE" = "xDebug"
221  then
222    COMMON_FLAGS=`echo " ${COMMON_FLAGS} " | \
223              sed -e 's/ -O[0-9]* / /' \
224                  -e 's/-Wp,-D_FORTIFY_SOURCE=2/ /' \
225                  -e 's/ -unroll2 / /' \
226                  -e 's/ -ip / /' \
227                  -e 's/^ //' \
228                  -e 's/ $//'`
229  fi
230fi
231#
232export COMMON_FLAGS="$COMMON_FLAGS -DPERCONA_INNODB_VERSION=$PERCONA_SERVER_VERSION"
233export CFLAGS="$COMMON_FLAGS ${CFLAGS:-}"
234export CXXFLAGS="$COMMON_FLAGS ${CXXFLAGS:-}"
235#
236export MAKE_JFLAG="${MAKE_JFLAG:--j$PROCESSORS}"
237#
238# Create a temporary working directory
239INSTALLDIR="$(cd "$WORKDIR" && TMPDIR="$WORKDIR_ABS" mktemp -d percona-build.XXXXXX)"
240INSTALLDIR="$WORKDIR_ABS/$INSTALLDIR"   # Make it absolute
241
242# Test jemalloc directory
243if test "x$WITH_JEMALLOC" != "x"
244then
245    if ! test -d "$WITH_JEMALLOC"
246    then
247        echo >&2 "Jemalloc dir $WITH_JEMALLOC does not exist"
248        exit 1
249    fi
250
251    JEMALLOCDIR="$(cd "$WITH_JEMALLOC"; pwd)"
252fi
253
254# Build
255(
256    rm -rf "$WORKDIR_ABS/bld"
257    mkdir "$WORKDIR_ABS/bld"
258    cd "$WORKDIR_ABS/bld"
259
260    cmake $SOURCEDIR ${CMAKE_OPTS:-} -DBUILD_CONFIG=mysql_release \
261        -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-RelWithDebInfo} \
262        $DEBUG_EXTRA \
263        -DWITH_EMBEDDED_SERVER=OFF \
264        -DFEATURE_SET=community \
265        -DENABLE_DTRACE=OFF \
266        -DWITH_SSL="$WITH_SSL_TYPE" \
267        -DWITH_ZLIB=system \
268        -DCMAKE_INSTALL_PREFIX="/usr/local/$PRODUCT_FULL" \
269        -DMYSQL_DATADIR="/usr/local/$PRODUCT_FULL/data" \
270        -DCOMPILATION_COMMENT="$COMMENT" \
271        -DWITH_PAM=ON \
272        -DWITH_ROCKSDB=ON \
273        -DROCKSDB_DISABLE_MARCH_NATIVE=1 \
274        -DROCKSDB_DISABLE_AVX2=1 \
275        -DWITH_INNODB_MEMCACHED=ON \
276        -DDOWNLOAD_BOOST=1 \
277        -DWITH_SCALABILITY_METRICS=ON \
278        -DWITH_BOOST="$WORKDIR_ABS/libboost" \
279        $WITH_MECAB_OPTION $OPENSSL_INCLUDE $OPENSSL_LIBRARY $CRYPTO_LIBRARY
280
281    make $MAKE_JFLAG $QUIET
282    make DESTDIR="$INSTALLDIR" install
283
284    # Build jemalloc
285    if test "x$WITH_JEMALLOC" != x
286    then
287    (
288        cd "$JEMALLOCDIR"
289
290        unset CFLAGS
291        unset CXXFLAGS
292
293        ./autogen.sh
294        ./configure --prefix="/usr/local/$PRODUCT_FULL/" \
295                --libdir="/usr/local/$PRODUCT_FULL/lib/mysql/"
296        make $MAKE_JFLAG
297        make DESTDIR="$INSTALLDIR" install_lib_shared
298
299        # Copy COPYING file
300        cp COPYING "$INSTALLDIR/usr/local/$PRODUCT_FULL/COPYING-jemalloc"
301    )
302    fi
303)
304
305# Patch needed libraries
306(
307    LIBLIST="libcrypto.so libssl.so libreadline.so libtinfo.so libsasl2.so libssl3.so libsmime3.so libnss3.so libnssutil3.so libplds4.so libplc4.so libnspr4.so libncurses.so"
308    DIRLIST="bin lib lib/private lib/mysql/plugin"
309
310    LIBPATH=""
311
312    function gather_libs {
313        local elf_path=$1
314        for lib in ${LIBLIST}; do
315            for elf in $(find ${elf_path} -maxdepth 1 -exec file {} \; | grep 'ELF ' | cut -d':' -f1); do
316                IFS=$'\n'
317                for libfromelf in $(ldd ${elf} | grep ${lib} | awk '{print $3}'); do
318                    lib_realpath="$(readlink -f ${libfromelf})"
319                    lib_realpath_basename="$(basename $(readlink -f ${libfromelf}))"
320                    lib_without_version_suffix=$(echo ${lib_realpath_basename} | awk -F"." 'BEGIN { OFS = "." }{ print $1, $2}')
321
322                    if [ ! -f "lib/private/${lib_realpath_basename}" ] && [ ! -L "lib/private/${lib_without_version_suffix}" ]; then
323
324                        echo "Copying lib ${lib_realpath_basename}"
325                        cp ${lib_realpath} lib/private
326
327                        echo "Symlinking lib from ${lib_realpath_basename} to ${lib_without_version_suffix}"
328                        cd lib/
329                        ln -s private/${lib_realpath_basename} ${lib_without_version_suffix}
330                        cd -
331                        if [ ${lib_realpath_basename} != ${lib_without_version_suffix} ]; then
332                            cd lib/private
333                            ln -s ${lib_realpath_basename} ${lib_without_version_suffix}
334                            cd -
335                        fi
336
337                        patchelf --set-soname ${lib_without_version_suffix} lib/private/${lib_realpath_basename}
338
339                        LIBPATH+=" $(echo ${libfromelf} | grep -v $(pwd))"
340                    fi
341                done
342                unset IFS
343            done
344        done
345    }
346
347    function set_runpath {
348        # Set proper runpath for bins but check before doing anything
349        local elf_path=$1
350        local r_path=$2
351        for elf in $(find ${elf_path} -maxdepth 1 -exec file {} \; | grep 'ELF ' | cut -d':' -f1); do
352            echo "Checking LD_RUNPATH for ${elf}"
353            if [ -z $(patchelf --print-rpath ${elf}) ]; then
354                echo "Changing RUNPATH for ${elf}"
355                patchelf --set-rpath ${r_path} ${elf}
356            fi
357        done
358    }
359
360    function replace_libs {
361        local elf_path=$1
362        for libpath_sorted in ${LIBPATH}; do
363            for elf in $(find ${elf_path} -maxdepth 1 -exec file {} \; | grep 'ELF ' | cut -d':' -f1); do
364                LDD=$(ldd ${elf} | grep ${libpath_sorted}|head -n1|awk '{print $1}')
365                lib_realpath_basename="$(basename $(readlink -f ${libpath_sorted}))"
366                lib_without_version_suffix="$(echo ${lib_realpath_basename} | awk -F"." 'BEGIN { OFS = "." }{ print $1, $2}')"
367                if [[ ! -z $LDD  ]]; then
368                    echo "Replacing lib ${lib_realpath_basename} to ${lib_without_version_suffix} for ${elf}"
369                    patchelf --replace-needed ${LDD} ${lib_without_version_suffix} ${elf}
370                fi
371            done
372        done
373    }
374
375    function check_libs {
376        local elf_path=$1
377        for elf in $(find ${elf_path} -maxdepth 1 -exec file {} \; | grep 'ELF ' | cut -d':' -f1); do
378            if ! ldd ${elf}; then
379                exit 1
380            fi
381        done
382    }
383
384    function link {
385        if [ ! -d lib/private ]; then
386            mkdir -p lib/private
387        fi
388        # Gather libs
389        for DIR in $DIRLIST; do
390            gather_libs ${DIR}
391        done
392        # Set proper runpath
393        set_runpath bin '$ORIGIN/../lib/private/'
394        set_runpath lib '$ORIGIN/private/'
395        set_runpath lib/mysql/plugin '$ORIGIN/../../private/'
396        set_runpath lib/private '$ORIGIN'
397        # Replace libs
398        for DIR in $DIRLIST; do
399            replace_libs ${DIR}
400        done
401        # Make final check in order to determine any error after linkage
402        for DIR in $DIRLIST; do
403            check_libs ${DIR}
404        done
405    }
406
407    if [[ $CMAKE_BUILD_TYPE != "Debug" ]]; then
408        mkdir $INSTALLDIR/usr/local/minimal
409        cp -r "$INSTALLDIR/usr/local/$PRODUCT_FULL" "$INSTALLDIR/usr/local/minimal/$PRODUCT_FULL-minimal"
410    fi
411
412    # NORMAL TARBALL
413    cd "$INSTALLDIR/usr/local/$PRODUCT_FULL"
414    link
415
416    # MIN TARBALL
417    if [[ $CMAKE_BUILD_TYPE != "Debug" ]]; then
418        cd "$INSTALLDIR/usr/local/minimal/$PRODUCT_FULL-minimal"
419        rm -rf mysql-test 2> /dev/null
420        find . -type f -exec file '{}' \; | grep ': ELF ' | cut -d':' -f1 | xargs strip --strip-unneeded
421        link
422    fi
423)
424
425# Package the archive
426(
427    cd "$INSTALLDIR/usr/local/"
428    #PS-4854 Percona Server for MySQL tarball without AGPLv3 dependency/license
429    find $PRODUCT_FULL -type f -name 'COPYING.AGPLv3' -delete
430    $TAR --owner=0 --group=0 -czf "$WORKDIR_ABS/$PRODUCT_FULL.tar.gz" $PRODUCT_FULL
431
432    if [[ $CMAKE_BUILD_TYPE != "Debug" ]]; then
433        cd "$INSTALLDIR/usr/local/minimal/"
434        find $PRODUCT_FULL-minimal -type f -name 'COPYING.AGPLv3' -delete
435        $TAR --owner=0 --group=0 -czf "$WORKDIR_ABS/$PRODUCT_FULL-minimal.tar.gz" $PRODUCT_FULL-minimal
436    fi
437)
438
439# Clean up
440rm -rf "$INSTALLDIR"
441rm -rf "$WORKDIR_ABS/libboost"
442rm -rf "$WORKDIR_ABS/bld"
443
444