1#!/bin/bash
2#
3#  This file is part of nzbget
4#
5#  Copyright (C) 2015-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
6#
7#  This program is free software; you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation; either version 2 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  You should have received a copy of the GNU General Public License
18#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20
21# Setup strict bash error handling
22set -o nounset
23set -o errexit
24
25# Uncomment next line for debuging
26#set -x
27
28ALLTARGETS="dist i686 x86_64 armel armhf aarch64 mipsel mipseb ppc6xx ppc500 x86_64-bsd i686-ndk armhf-ndk aarch64-ndk"
29ROOT=`pwd`
30ROOTPATH=$PATH
31OUTPUTDIR=$ROOT/output
32CROSSCLANG="clang-4.0"
33
34# Configuration
35BUILD=no
36PLATFORMS=""
37TARGETS=""
38OUTPUTS=""
39REVISION=""
40BRANCH=""
41CONFIGS=""
42CLEAN=no
43PCH=no
44COREX="1"
45
46PrintUsage()
47{
48    echo "Usage:"
49    echo "  $(basename $0) [targets] [output] [revision] [reppath] [configs] [cleanup] [pch] [corex]"
50    echo "    targets   : all (default) $ALLTARGETS"
51    echo "    output    : bin installer"
52    echo "    revision  : head (default) work <commit-hash>"
53    echo "    branch    : develop (default) master tags/XXX branches/XXX"
54    echo "    configs   : release debug (default) release-nostrip debug-strip"
55    echo "    cleanup   : cleanup output directory before building"
56    echo "    pch       : create and use precompiled headers"
57    echo "    corex     : multicore make (x is a number of threads)"
58    echo
59}
60
61ParseCommandLine()
62{
63    for PARAM in "$@"
64    do
65        case $PARAM in
66            release|release-nostrip|debug|debug-strip)
67                # using xargs to trim spaces
68                CONFIGS=`echo "$CONFIGS $PARAM" | xargs`
69                ;;
70            master|develop|tags/*)
71                BRANCH="$PARAM"
72                ;;
73            branches/*)
74                BRANCH="${PARAM:9}"
75                ;;
76            head|work|[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*)
77                REVISION="$PARAM"
78                ;;
79            clean|cleanup)
80                CLEAN=yes
81                ;;
82            pch)
83                PCH=yes
84                ;;
85            core[1-9])
86                COREX="${PARAM:4}"
87                ;;
88            bin|installer)
89                # using xargs to trim spaces
90                OUTPUTS=`echo "$OUTPUTS $PARAM" | xargs`
91                ;;
92            *)
93                if [[ " $ALLTARGETS " == *" $PARAM "* ]]; then
94                    # using xargs to trim spaces
95                    TARGETS=`echo "$TARGETS $PARAM" | xargs`
96                    if [ "$PARAM" == "all" ]; then
97                        PARAM=$ALLTARGETS
98                    fi
99                elif [ -d toolchain/$PARAM ]; then
100                    # non-standard target but the toolchain exist
101                    # using xargs to trim spaces
102                    TARGETS=`echo "$TARGETS $PARAM" | xargs`
103                else
104                    echo "Invalid parameter: $PARAM"
105                    exit 1
106                fi
107                ;;
108        esac
109
110    done
111
112    if [ "$TARGETS" == "" ]; then
113        TARGETS="$ALLTARGETS"
114    fi
115
116    if [ "$OUTPUTS" == "" ]; then
117        OUTPUTS="bin installer"
118    fi
119
120    if [ "$REVISION" == "" ]; then
121        REVISION="head"
122    fi
123
124    if [ "$BRANCH" == "" ]; then
125        BRANCH="develop"
126    fi
127
128    if [ "$CONFIGS" == "" ]; then
129        CONFIGS="release debug"
130    fi
131
132    for TARGET in $TARGETS
133    do
134        if [[ $TARGET == *-bsd ]]; then
135            PLATFORM="freebsd"
136        elif [[ $TARGET == *-ndk ]]; then
137            PLATFORM="android"
138        else
139            PLATFORM="linux"
140        fi
141        if [[ " $PLATFORMS " != *" $PLATFORM "* ]]; then
142              PLATFORMS=`echo "$PLATFORMS $PLATFORM" | xargs`
143        fi
144    done
145}
146
147PrintConfig()
148{
149    echo "Active configuration:"
150    echo "  platforms: $PLATFORMS"
151    echo "  targets  : $TARGETS"
152    echo "  outputs  : $OUTPUTS"
153    echo "  revision : $REVISION"
154    echo "  branch   : $BRANCH"
155    echo "  configs  : $CONFIGS"
156    echo "  cleanup  : $CLEAN"
157    echo "  pch      : $PCH"
158    echo "  cores    : $COREX"
159    echo
160}
161
162UpdateFromRepository()
163{
164    if [ ! -d nzbget ]; then
165        echo "Initial checkout"
166        git clone https://github.com/nzbget/nzbget.git
167    fi
168
169    cd nzbget
170
171    BUILDDIR=$ROOT/nzbget
172
173    cd $BUILDDIR
174
175    if [ $REVISION != "work" ]; then
176        echo "Updating to $REVISION"
177        git pull
178        git checkout $BRANCH
179        if [ $REVISION != "head" ]; then
180            git checkout $REVISION
181        fi
182        touch Makefile.in configure config.h.in
183
184        echo "Updating root certificates"
185        cd ../setup
186        curl --remote-name --time-cond cacert.pem https://curl.se/ca/cacert.pem
187        cd $BUILDDIR
188    fi
189}
190
191DetectVersionRevision()
192{
193    VERSION=`grep "AC_INIT(nzbget, " configure.ac`
194    VERSION=`expr "$VERSION" : '.*, \(.*\),.*)'`
195
196    # Building revision name
197    B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p' | sed 's/ /-/g' | sed 's/(//g' | sed 's/)//g'`
198    M=`git status --porcelain`
199    if [ "$M" != "" ]; then
200        M="M"
201    fi
202    if [ "$B" == "master" ]; then
203        REVISION="$M"
204    elif [ "$B" == "develop" ]; then
205        REVISION=`git rev-list HEAD | wc -l | xargs`
206        REVISION="${REVISION}$M"
207    else
208        REVISION=`git rev-list HEAD | wc -l | xargs`
209        REVISION="${REVISION}$M-($B)"
210    fi
211}
212
213ConstructOutputFilename()
214{
215    BASENAME="nzbget-$VERSION"
216    if [ `expr "$VERSION" : ".*-testing"` != 0 ]; then
217        BASENAME="$BASENAME-r$REVISION"
218    fi
219}
220
221ConstructSuffix()
222{
223    case $CONFIG in
224        release)
225            SUFFIX=""
226            ;;
227        debug)
228            SUFFIX="-debug"
229            ;;
230        debug-strip)
231            SUFFIX="-debug-strip"
232            ;;
233        release-nostrip)
234            SUFFIX="-nostrip"
235            ;;
236    esac
237
238    if [ $PLATFORM == "freebsd" ]; then
239        PLATSUFF="-bsd"
240    elif [ $PLATFORM == "android" ]; then
241        PLATSUFF="-ndk"
242    else
243        PLATSUFF=""
244    fi
245}
246
247ConstructArchParams()
248{
249    ARCH=$TARGET
250    case $TARGET in
251        mipseb)
252            ARCH=mips
253            ;;
254        arm*)
255            ARCH=arm
256            ;;
257        ppc*)
258            ARCH=powerpc
259            ;;
260    esac
261
262    TOOLCHAIN_ROOT=$ROOT/toolchain/$TARGET$PLATSUFF
263    TOOLPATH=$TOOLCHAIN_ROOT/output/host/usr/bin
264    PATH=$TOOLPATH:$ROOTPATH
265    STAGING="$TOOLCHAIN_ROOT/output/staging"
266    case $PLATFORM in
267        linux)
268            TOOLKIND=buildroot
269            ARCH=$ARCH-linux
270        ;;
271        android)
272            TOOLKIND=ndk
273            ARCH=$ARCH-linux-android
274            if [ "$ARCH" == "arm-linux-android" ]; then
275                ARCH="arm-linux-androideabi"
276            fi
277        ;;
278        freebsd)
279            TOOLKIND=crossclang
280            ARCH=$ARCH-pc-freebsd
281            SYSROOT="$TOOLCHAIN_ROOT/sysroot"
282        ;;
283    esac
284}
285
286ConfigureTarget()
287{
288    STRIP=""
289    if [ $CONFIG == "debug-strip" -o $CONFIG == "release" ]; then
290        STRIP="-s"
291    fi
292
293    DEBUG=""
294    if [ $CONFIG != "release" ]; then
295        DEBUG="-g"
296    fi
297
298    LUBACKTRACE=""
299    if [ -f "$STAGING/lib/libubacktrace.so.1" ] ; then
300        LUBACKTRACE="-lubacktrace"
301    fi
302
303    case "$TOOLKIND-$CONFIG" in
304        buildroot-debug|buildroot-debug-strip)
305            LDFLAGS="-static $STRIP" \
306                CXXFLAGS="-std=c++14 -g -fasynchronous-unwind-tables" \
307                LIBS="-lcrypto -ldl -lz -Wl,--whole-archive -lpthread -Wl,--no-whole-archive $LUBACKTRACE" \
308                ./configure --disable-dependency-tracking --host=$ARCH --enable-debug
309            ;;
310        buildroot-release|buildroot-release-nostrip)
311            LDFLAGS="-static $STRIP" \
312                CXXFLAGS="-std=c++14 -O2 $DEBUG" \
313                LIBS="-lcrypto -ldl -lz -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" \
314                ./configure --disable-dependency-tracking --host=$ARCH
315            ;;
316        ndk-debug|ndk-debug-strip)
317            LDFLAGS="-static -static-libstdc++ $STRIP" \
318                CXXFLAGS="-std=c++14 -g -fasynchronous-unwind-tables" \
319                CXX="$ARCH-clang++" \
320                PKG_CONFIG_LIBDIR="$STAGING/usr/lib/pkgconfig" \
321                ./configure --disable-dependency-tracking --disable-largefile --host=$ARCH --enable-debug
322            ;;
323        ndk-release|ndk-release-nostrip)
324            LDFLAGS="-static -static-libstdc++ $STRIP" \
325                CXXFLAGS="-std=c++14 -O2 $DEBUG" \
326                CXX="$ARCH-clang++" \
327                PKG_CONFIG_LIBDIR="$STAGING/usr/lib/pkgconfig" \
328                ./configure --disable-dependency-tracking --disable-largefile --host=$ARCH
329            ;;
330        crossclang-debug|crossclang-debug-strip)
331            CXX="$CROSSCLANG" \
332                LDFLAGS="-static $STRIP -fuse-ld=lld --target=$ARCH -lc++ -lm --sysroot=$SYSROOT" \
333                CXXFLAGS="-g --target=$ARCH --sysroot=$SYSROOT -I$SYSROOT/usr/include/c++/v1" \
334                PKG_CONFIG_LIBDIR="$STAGING/usr/lib/pkgconfig" \
335                ./configure --disable-dependency-tracking --host=$ARCH --enable-debug
336                ;;
337        crossclang-release|crossclang-release-nostrip)
338            CXX="$CROSSCLANG" \
339                LDFLAGS="-static $STRIP -fuse-ld=lld --target=$ARCH -lc++ -lm --sysroot=$SYSROOT" \
340                CXXFLAGS="-O2 $DEBUG --target=$ARCH --sysroot=$SYSROOT -I$SYSROOT/usr/include/c++/v1" \
341                PKG_CONFIG_LIBDIR="$STAGING/usr/lib/pkgconfig" \
342                ./configure --disable-dependency-tracking --host=$ARCH --enable-debug
343                ;;
344    esac
345}
346
347PrecompileHeaders()
348{
349    rm -f nzbget.h.*
350
351    OPTIM=""
352    if [ $CONFIG == "release" -o $CONFIG == "release-nostrip" ]; then
353        OPTIM="-O2"
354    fi
355
356    if [ $PCH == "yes" -a $TOOLKIND == "buildroot" ]; then
357        $ARCH-g++ -std=c++14 -DHAVE_CONFIG_H \
358            -I. -I$STAGING/usr/include -I$STAGING/usr/include/libxml2 \
359            -g $OPTIM daemon/main/nzbget.h -o nzbget.h.gch
360    fi
361
362    if [ $PCH == "yes" -a $TOOLKIND == "ndk" ]; then
363        $ARCH-clang++ -std=c++14 -DHAVE_CONFIG_H \
364            -I. -I$STAGING/usr/include -I$STAGING/usr/include/libxml2 \
365            -g $OPTIM -x c++-header daemon/main/nzbget.h -o nzbget.h.pch
366        CXXFLAGS=`sed -n 's:^CXXFLAGS =.\(.*\):\1:p' Makefile`
367        sed 's:^CXXFLAGS = :CXXFLAGS = -include-pch nzbget.h.pch :' -i Makefile
368        sed "s: CXXFLAGS+=: CXXFLAGS=$CXXFLAGS :" -i Makefile
369    fi
370}
371
372PackTarget()
373{
374    rm -r -f $OUTPUTDIR/install
375    make DESTDIR=$OUTPUTDIR/install install
376
377    cd $OUTPUTDIR
378    rm -r -f nzbget
379    mkdir -p nzbget
380    mv install/usr/local/bin/nzbget nzbget
381    mv install/usr/local/share/doc/nzbget/* nzbget
382    mv install/usr/local/share/nzbget/webui nzbget
383    mv install/usr/local/share/nzbget/scripts nzbget
384    CONFTEMPLATE=nzbget/webui/nzbget.conf.template
385    mv install/usr/local/share/nzbget/nzbget.conf $CONFTEMPLATE
386
387    rm -r -f $OUTPUTDIR/install
388
389    # adjusting nzbget.conf
390    sed 's:^MainDir=.*:MainDir=${AppDir}/downloads:' -i $CONFTEMPLATE
391    sed 's:^DestDir=.*:DestDir=${MainDir}/completed:' -i $CONFTEMPLATE
392    sed 's:^InterDir=.*:InterDir=${MainDir}/intermediate:' -i $CONFTEMPLATE
393    sed 's:^WebDir=.*:WebDir=${AppDir}/webui:' -i $CONFTEMPLATE
394    sed 's:^ScriptDir=.*:ScriptDir=${AppDir}/scripts:' -i $CONFTEMPLATE
395    sed 's:^LogFile=.*:LogFile=${MainDir}/nzbget.log:' -i $CONFTEMPLATE
396    sed 's:^ConfigTemplate=.*:ConfigTemplate=${AppDir}/webui/nzbget.conf.template:' -i $CONFTEMPLATE
397    sed 's:^AuthorizedIP=.*:AuthorizedIP=127.0.0.1:' -i $CONFTEMPLATE
398
399    tar -czf $BASENAME-bin-$PLATFORM-$TARGET$SUFFIX.tar.gz nzbget
400
401    rm -r -f nzbget
402}
403
404BuildTarget()
405{
406    echo "Building $TARGET ($PLATFORM $CONFIG)"
407
408    cd $BUILDDIR
409
410    ConstructArchParams
411
412    ConfigureTarget
413
414    make clean
415
416    PrecompileHeaders
417
418    make -j $COREX
419
420    PackTarget
421
422    echo "Completed build in `pwd` for $TARGET ($CONFIG)"
423}
424
425BuildInstaller()
426{
427    echo "Creating installer for $PLATFORM $CONFIG..."
428
429    cd $OUTPUTDIR
430
431    # checking if all targets exists
432    for TARGET in $TARGETS
433    do
434        ALLEXISTS="yes"
435        if [ $TARGET != "dist" ]; then
436            if [ ! -f $BASENAME-bin-$PLATFORM-$TARGET$SUFFIX.tar.gz ]; then
437                echo "Could not find $BASENAME-bin-$PLATFORM-$TARGET$SUFFIX.tar.gz"
438                ALLEXISTS="no"
439            fi
440        fi
441    done
442
443    if [ "$ALLEXISTS" == "no" ]; then
444        exit 1;
445    fi
446
447    echo "Unpacking targets..."
448    rm -r -f nzbget
449    for TARGET in $TARGETS
450    do
451        ALLEXISTS="yes"
452        if [ "$TARGET" != "dist" ]; then
453            tar -xzf $BASENAME-bin-$PLATFORM-$TARGET$SUFFIX.tar.gz
454            mv nzbget/nzbget nzbget/nzbget-$TARGET
455            cp ../setup/unrar-$TARGET$PLATSUFF nzbget/unrar-$TARGET
456            cp ../setup/7za-$TARGET$PLATSUFF nzbget/7za-$TARGET
457        fi
458    done
459
460    # adjusting nzbget.conf
461    sed 's:^UnrarCmd=unrar:UnrarCmd=${AppDir}/unrar:' -i nzbget/webui/nzbget.conf.template
462    sed 's:^SevenZipCmd=7z:SevenZipCmd=${AppDir}/7za:' -i nzbget/webui/nzbget.conf.template
463    sed 's:^CertStore=.*:CertStore=${AppDir}/cacert.pem:' -i nzbget/webui/nzbget.conf.template
464    sed 's:^CertCheck=.*:CertCheck=yes:' -i nzbget/webui/nzbget.conf.template
465
466    INSTFILE=$BASENAME-bin-$PLATFORM$SUFFIX.run
467
468    echo "Building installer package..."
469    cp $BUILDDIR/linux/installer.sh $INSTFILE
470    cp $BUILDDIR/linux/package-info.json nzbget/webui
471    cp $BUILDDIR/linux/install-update.sh nzbget
472    cp $BUILDDIR/pubkey.pem nzbget
473    cp ../setup/license-unrar.txt nzbget
474    cp ../setup/license-7zip.txt nzbget
475    cp ../setup/cacert.pem nzbget
476
477    # adjusting update config file
478    sed "s:linux:$PLATFORM:" -i nzbget/webui/package-info.json
479    sed "s:linux:$PLATFORM:" -i nzbget/install-update.sh
480
481    # creating payload
482    cd nzbget
483    tar czf - * > ../$INSTFILE.data
484    cd ..
485
486    # creating installer script
487    sed "s:^TITLE=$:TITLE=\"$BASENAME$SUFFIX\":" -i $INSTFILE
488    sed "s:^PLATFORM=$:PLATFORM=\"$PLATFORM\":" -i $INSTFILE
489    DISTTARGETS="${TARGETS/dist/}"
490    DISTTARGETS=`echo "$DISTTARGETS" | xargs`
491    sed "s:^DISTARCHS=$:DISTARCHS=\"$DISTTARGETS\":" -i $INSTFILE
492
493    MD5=`md5sum "$INSTFILE.data" | cut -b-32`
494    sed "s:^MD5=$:MD5=\"$MD5\":" -i $INSTFILE
495
496    PAYLOAD=`stat -c%s "$INSTFILE.data"`
497    PAYLOADLEN=${#PAYLOAD}
498
499    HEADER=`stat -c%s "$INSTFILE"`
500    HEADERLEN=${#HEADER}
501    HEADER=`expr $HEADER + $HEADERLEN + $PAYLOADLEN`
502
503    TOTAL=`expr $HEADER + $PAYLOAD`
504    TOTALLEN=${#TOTAL}
505
506    HEADER=`expr $HEADER - $PAYLOADLEN + $TOTALLEN`
507    TOTAL=`expr $TOTAL - $PAYLOADLEN + $TOTALLEN`
508
509    sed "s:^HEADER=$:HEADER=$HEADER:" -i $INSTFILE
510    sed "s:^TOTAL=$:TOTAL=$TOTAL:" -i $INSTFILE
511
512    # attaching payload
513    cat $INSTFILE.data >> $INSTFILE
514    rm $INSTFILE.data
515    chmod +x $INSTFILE
516
517    rm -r nzbget
518}
519
520BuildDist()
521{
522    echo "Building dist"
523
524    cd $BUILDDIR
525    ./configure --disable-dependency-tracking --disable-cpp-check
526    make dist
527    cp nzbget-$VERSION.tar.gz $OUTPUTDIR/$BASENAME.tar.gz
528}
529
530BuildConfig()
531{
532    ConstructSuffix
533
534    for OUTPUT in $OUTPUTS; do
535        if [ $OUTPUT == "bin" ]; then
536            for TARGET in $TARGETS; do
537                if [ $TARGET == "dist" ]; then
538                    if [ ! -f $OUTPUTDIR/$BASENAME.tar.gz ]; then
539                        BuildDist
540                    fi
541                else
542                    BuildTarget
543                fi
544            done
545        elif [ $OUTPUT == "installer" ]; then
546            BuildInstaller
547        fi
548    done
549}
550
551FilterTargets()
552{
553    TARGETS=""
554    for TARGET in $BUILDTARGETS
555    do
556        if [[ $TARGET == *-bsd ]]; then
557            PLAT="freebsd"
558            TARGET="${TARGET%-bsd}"
559        elif [[ $TARGET == *-ndk ]]; then
560            PLAT="android"
561            TARGET="${TARGET%-ndk}"
562        else
563            PLAT="linux"
564        fi
565        if [ $PLATFORM == $PLAT ]; then
566              TARGETS=`echo "$TARGETS $TARGET" | xargs`
567        fi
568    done
569}
570
571CleanupIfNecessary()
572{
573    if [ $CLEAN == "yes" ]; then
574        rm -r -f $OUTPUTDIR/*
575    fi
576}
577
578##### Main script body #####
579
580# Parsing command line
581PrintUsage
582ParseCommandLine $@
583PrintConfig
584
585# Checkout and update from git
586UpdateFromRepository
587
588# File name format for output files
589DetectVersionRevision
590ConstructOutputFilename
591
592# Building
593mkdir -p $OUTPUTDIR
594CleanupIfNecessary
595
596BUILDTARGETS=$TARGETS
597
598for PLATFORM in $PLATFORMS; do
599    FilterTargets
600    for CONFIG in $CONFIGS; do
601        BuildConfig
602    done
603done
604
605