#!/usr/bin/env bash # vim: set autoindent smartindent ts=4 sw=4 sts=4 noet filetype=sh: ############################################################################### ## Based on instructions from: ## https://github.com/RMerl/asuswrt-merlin/wiki/Compiling-from-source-using-a-Debian-based-Linux-Distribution ## https://github.com/RMerl/asuswrt-merlin/wiki/Compile-Firmware-from-source-using-Ubuntu ############################################################################### [[ -n "$DEBUG" ]] && set -x pushd $(dirname $0) > /dev/null; CURRABSPATH=$(readlink -nf "$(pwd)"); popd > /dev/null [[ -t 1 ]] && { G="\e[1;32m"; R="\e[1;31m"; Z="\e[0m"; B="\e[1;34m"; W="\e[1;37m"; Y="\e[1;33m"; } TESTED_ON="tested on: ${W}Ubuntu 12.04 & 14.04${Z} and ${W}Debian 7.8${Z}" REPORT_BACK="${W}Please report back${Z} about successes and failures at (e.g. by opening a ticket)" TOOLCHAIN_BINARIES="mipsel-linux-addr2line mipsel-linux-ar mipsel-linux-as mipsel-linux-c++ mipsel-linux-cc mipsel-linux-c++filt mipsel-linux-cpp mipsel-linux-g++ mipsel-linux-gcc mipsel-linux-gcc-4.2.4 mipsel-linux-gcov mipsel-linux-gprof mipsel-linux-ld mipsel-linux-nm mipsel-linux-objcopy mipsel-linux-objdump mipsel-linux-ranlib mipsel-linux-readelf mipsel-linux-size mipsel-linux-strings mipsel-linux-strip" INSTALL_PREREQ="" APTI="apt-get --no-install-recommends install" INTERACTIVE=1 help_syntax() # show a brief help screen { echo -e "${W}SYNTAX:${Z} ${0##*/} [-p|--prereq] [-y|--yes] [-h|--help] [path]\n" echo -e "\twhere:\n\ \t* ${W}-p, --prereq${Z} Install all the prerequisites using 'apt-get'\n\ \t (please note that this requires you to be a sudoer)\n\ \t* ${W}-y, --yes${Z} Automatic yes to prompts\n\ \t (assume "yes" as answer to all prompts and run non-interactively)\n\ \t* ${W}path${Z} is a directory containing the asuswrt-merlin sources\n\ \t (default is the directory with this script: $CURRABSPATH)\n\ \t* ${W}router-model${Z} is the model name you'd pass to make\n\ \t (e.g. rt-n66u, rt-ac66u etc, see ${W}README.md${Z})\n" echo -e "Variables:\n\ \t${W}USE_SUDO${Z} if set will 'install' and use tools in /opt, but requires ${W}sudo${Z}.\n\ \t${W}FORCE_UNSUPPORTED${Z} if set will proceed even if the system doesn't match prerequisites 100%.\n\ \t${W}TMUX_PANE${Z} if unset/empty will suppress logging inside a Tmux session." } # parse arguments while [[ $# > 0 ]]; do case "$1" in -p|--prereq) INSTALL_PREREQ=1 shift ;; -y|--yes) INTERACTIVE="" shift ;; -h|--help) help_syntax exit 0 ;; -*) echo "unknown option: $1" >&2; exit 1 ;; *) ROUTERMDL="${1}" BASEDIR="${2:-$CURRABSPATH}" shift; shift ;; esac done if [[ ! -n "$INTERACTIVE" ]]; then APTI="$APTI -y" export DEBIAN_FRONTEND=noninteractive fi let TIME_START=$(date +%s) CURRDATE=$(date +'%Y-%m-%dT%H-%M-%S') _TMUX_LOG="$BASEDIR/tmux-${CURRDATE}.log" show_time_diff() { local START=$1 local END=$2 local MSG=$3 [[ -n "$MSG" ]] || MSG="Runtime: %s" local DIFF=$((END-START)) local DIFF_MIN=$((DIFF/60)) local DIFF_SEC=$((DIFF%60)) printf "$MSG\n" $(printf "%d:%02d" "$DIFF_MIN" "$DIFF_SEC") } fatal() # show the given error, include the help screen if requested and then exit with status 1 { [[ "x$1" == "xhelp" ]] && { SHOWHELP=1; shift; } echo -e "${R}ERROR:${Z} ${1:-}" ((SHOWHELP)) && help_syntax exit 1 } cleanup() # turn off tmux logging if enabled before, any other common cleanup tasks will be added here { if [[ -n "$TMUX_PANE" ]]; then echo -e "\n${Y}CLEANUP${Z}: turning off tmux logging ($_TMUX_LOG)" tmux pipe-pane -t $TMUX_PANE if [[ -f "$_TMUX_LOG" ]]; then sed -i.nocolor -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" "$_TMUX_LOG" && rm -f "${_TMUX_LOG}.nocolor" sed -i.bak -r 's#'"$BASEDIR"'#\$BASEDIR#g' "$_TMUX_LOG" && rm -f "${_TMUX_LOG}.bak" fi fi show_time_diff $TIME_START $(date +%s) } enable_tmux_logging() { if [[ -n "$TMUX_PANE" ]]; then echo -e "${Y}NOTE:${Z} turning on tmux logging ($_TMUX_LOG)" tmux pipe-pane -t $TMUX_PANE "cat >> $_TMUX_LOG" && trap cleanup EXIT fi } cleanup_and_revert() # reverts parts of the effects of this script, then calls cleanup() { echo -e "\n${Y}CLEANUP${Z}: extended cleanup of source tree changes" # Changed files will be reverted find "$BASEDIR" -type f -name '*.BAK-build-image'|while read fname; do ( set -x && mv "$fname" "${fname%%.BAK-build-image}" ) done # Simply remove proxy.h if we moved it there rm -f "$BASEDIR/release/src/router/neon/proxy.h" # Unlink the toolchain symlink, in case we were asked to put one [[ -n "$USE_SUDO" ]] && sudo unlink /opt/brcm cleanup } install_required_packages() { ((PREREQ_MISSING_NUM > 0)) || { echo -e "${Y}NOTE:${Z} no missing package dependencies were found."; return 0; } local REQ_PKGS="${REQ_PKGS[*]}" [[ -n "$1" ]] && REQ_PKGS="$1" echo -e "${W}INSTALLATION OF PREREQUISITE PACKAGES" echo -e "=====================================${Z}" [[ $UID -eq 0 ]] || { echo -e "${Y}NOTE:${Z} This requires ${W}sudo${Z}, unless you simulate the installation only."; SUDO=sudo; } if [[ "$(dpkg --print-architecture)" == "amd64" ]]; then if [[ $(dpkg --print-foreign-architectures) =~ i386 ]]; then DPKG_ADD_ARCH="true" else DPKG_ADD_ARCH="dpkg --add-architecture i386" echo -e "${Y}NOTE:${Z} you are running a $(dpkg --print-architecture) system, but we need libelf1:i386. So we will need to run\n\t${W}${SUDO+$SUDO }${DPKG_ADD_ARCH}${Z}\n... prior to the ${W}apt-get${Z} invocation. This is just to inform you about the fact.\n" fi else DPKG_ADD_ARCH="true" fi echo -e "The command to be run is (beware of line breaks in overlong lines!):\n\n${W}${SUDO+$SUDO }$APTI ${Z}${REQ_PKGS}\n" if [[ -n $INTERACTIVE ]]; then echo -ne "Do you want to continue? ([${W}y${Z}]es/[${W}N${Z}]o/[${W}s${Z}]imulate)" read -p " " ans [[ $ans =~ ^[YySs]$ ]] || { echo -e "\n${Y}NOTE:${Z} aborted by user."; exit 1; } else ans=y fi if [[ $ans =~ ^[Ss]$ ]]; then ( set -x; ${DPKG_ADD_ARCH:-true} && $APTI -s ${REQ_PKGS} ) exit 0 else ( set -x; $SUDO $SHELL -c "$DPKG_ADD_ARCH && apt-get update && $APTI ${REQ_PKGS}" ) || fatal "an error occured when trying to install missing package dependencies." PREREQ_MISSING_NUM=0 fi } check_or_fail_os_prereq() { [[ -z "$FORCE_UNSUPPORTED" ]] && fatal "this script has only been ${TESTED_ON}. However, you're running ${W}$(lsb_release -si) $(lsb_release -sr) ($(lsb_release -sc))${Z}. If you'd like to try it regardless, make sure to set ${W}FORCE_UNSUPPORTED=1${Z} prior to invoking it." } check_runtime_requirements() { # Does this look like a Debian? [[ -e "/etc/debian_version" ]] || fatal "this script expects a Debian flavor. It has been ${TESTED_ON}." [[ -n $(type -t lsb_release) ]] || fatal "${W}lsb_release{$Z} is expected to be installed. That is normally the case on Debian and Ubuntu anyway." OS_REL=$(lsb_release -sr|cut -d . -f 1) # All those packages are required according to the asuswrt-merlin docs (exceptions: I replaced g++ and make by build-essential, libncurses-dev => libncurses5-dev) PREREQ_PKGS=(autoconf automake bash bison build-essential bzip2 diffutils file flex gawk gcc-multilib gettext gperf groff-base libncurses5-dev libexpat1-dev libslang2 libssl-dev libtool libxml-parser-perl patch perl pkg-config python sed shtool tar texinfo unzip zlib1g zlib1g-dev) # Figure out the Debian flavor case $(lsb_release -si) in Debian) ((OS_REL < 7)) && check_or_fail_os_prereq ;; LinuxMint) echo -e "${Y}WARNING:${Z} this script should for all practical purposes run on Linux Mint >= 13 (maya), but it's possible that it doesn't. ${REPORT_BACK}." ((OS_REL < 13)) && check_or_fail_os_prereq ((OS_REL >= 16)) && { PREREQ_PKGS+=(automake1.11 autopoint intltool libproxy-dev); UBUNTU_EXTRAS=1; } ;; Ubuntu) ((OS_REL < 12)) && check_or_fail_os_prereq local UBUREL=0x$(lsb_release -sr|tr -d '.') # On Ubuntu 13.10 and later we check for two additional packages ((UBUREL >= 0x1310)) && { PREREQ_PKGS+=(automake1.11 autopoint intltool libproxy-dev); UBUNTU_EXTRAS=1; } ;; *) echo -e "${Y}WARNING:${Z} this is an ${W}unsupported Debian flavor${Z}. It is possible, even likely, that this will fail. ${REPORT_BACK}." ;; esac # On x86_64 we also need two additional packages [[ "$(uname -m)" == "x86_64" ]] && PREREQ_PKGS+=(lib32z1-dev lib32stdc++6) # For ARM routers add libelf1 if [[ "$ARCH" == "ARM" ]]; then [[ "$(dpkg --print-architecture)" == "amd64" ]] && PREREQ_PKGS+=(libelf1:i386 libxml2-dev) || PREREQ_PKGS+=(libelf1 libxml2-dev) fi } tell_missing_packages() { local OTHER_ARCH_PREREQ_PKGS=() if [[ ${PREREQ_PKGS[*]} =~ :i386 ]] && [[ "$(dpkg --print-architecture)" == "amd64" ]]; then if [[ ! $(dpkg --print-foreign-architectures) =~ i386 ]]; then echo -e "${Y}NOTE:${Z} trying to find the list of missing packages may fail or yield an incomplete list.\n The reason is that you require a package from a foreign architecture not installed on your system: i386.\n You can still run this script with the ${W}--prereq${Z} option to fix missing dependencies." NEW_PREREQ_PKGS=() # Weed out packages which would cause errors for r in ${PREREQ_PKGS[*]}; do [[ $r =~ :i386 ]] && OTHER_ARCH_PREREQ_PKGS+=($r) || NEW_PREREQ_PKGS+=($r) done PREREQ_PKGS=(${NEW_PREREQ_PKGS[*]}) fi fi # Find the packages which are already installed, so we can remove them from the list of required packages local INST_ALREADY=($($APTI -s ${PREREQ_PKGS[*]}|awk '/is already the newest version\.$/ {print $1}')) PREREQ_MISSING=($(for pkg in $(echo ""|awk -f <(cat - <<-'EOF' # Embedded awk script BEGIN { EOF for r in ${PREREQ_PKGS[*]}; do echo " REQD[\"$r\"] = 0;"; done for i in ${INST_ALREADY[*]}; do echo " INST[\"$i\"] = 0;"; done cat - <<-'EOF' } END { for (i in INST) delete REQD[i] for (r in REQD) printf("%s\n", r) } EOF )); do echo "$pkg"; done|sort -u)) [[ ${#OTHER_ARCH_PREREQ_PKGS[@]} -gt 0 ]] && PREREQ_MISSING+=(${OTHER_ARCH_PREREQ_PKGS[*]}) PREREQ_MISSING_NUM=${#PREREQ_MISSING[*]} if ((PREREQ_MISSING_NUM > 0)); then echo -e "${R}ERROR:${Z} ${PREREQ_MISSING_NUM} required packages are missing on your system:\n\t${Z} ${PREREQ_MISSING[*]}\n" fi } ## HOOK INIT [[ -e "${CURRABSPATH%/}/.${0##*/}rc" ]] && { source "${CURRABSPATH%/}/.${0##*/}rc" || fatal "'${CURRABSPATH%/}/.${0##*/}rc' exists but could not be sourced successfully"; } function_exists() { declare -f $1 > /dev/null 2>&1 # use exit status from declare } ## MAIN [[ -n "$ROUTERMDL" ]] || fatal help "you have to ${W}give the router model${Z} to build for." # Match the router model to find the correct base directory for make # To find supported router models: # grep -hoP 'RT-(AC|N)\d+[URW]*(_V\d+)?' release/src-rt*/target.mak|sort -u OLD_ROUTERMDL=${ROUTERMDL^^} case ${ROUTERMDL^^} in # release/src-rt RT-N16) MKBASE="src-rt";; # release/src-rt-6.x RT-AC66|RT-AC66[URW]) ROUTERMDL="RT-AC66U" MKBASE="src-rt-6.x";; RT-N66|RT-N66[URW]) ROUTERMDL="RT-N66U" MKBASE="src-rt-6.x";; # release/src-rt-6.x.4708 RT-AC56|RT-AC56[URW]) ROUTERMDL="RT-AC56U" MKBASE="src-rt-6.x.4708";; RT-AC68|RT-AC68[URW]) ROUTERMDL="RT-AC68U" MKBASE="src-rt-6.x.4708";; RT-AC68_V2|RT-AC68[URW]_V2) ROUTERMDL="RT-AC68U_V2" MKBASE="src-rt-6.x.4708";; RT-AC69|RT-AC69[URW]) ROUTERMDL="RT-AC69U" MKBASE="src-rt-6.x.4708";; RT-AC87|RT-AC87[URW]) ROUTERMDL="RT-AC87U" MKBASE="src-rt-6.x.4708";; *) # anything unmatched if function_exists hook_unknown_router; then hook_unknown_router "${ROUTERMDL}" else fatal "unrecognized router model ${W}$ROUTERMDL${Z}. Either the command line or this script needs fixing [base]." fi;; esac case ${ROUTERMDL^^} in RT-N16|RT-AC66U|RT-N66U) ARCH="MIPS";; RT-AC56U|RT-AC68U|RT-AC68U_V2|RT-AC69U|RT-AC87U) ARCH="ARM";; *) if function_exists hook_unknown_router; then hook_unknown_router "${ROUTERMDL}" else fatal "unrecognized router model ${W}$ROUTERMDL${Z}. Either the command line or this script needs fixing [arch]." fi;; esac enable_tmux_logging function_exists hook_override_model && hook_override_model [[ "${OLD_ROUTERMDL^^}" == "${ROUTERMDL^^}" ]] || { echo -e "${W}INFO${Z}: your router model choice ${W}${OLD_ROUTERMDL^^}${Z} is firmware-compatible to ${ROUTERMDL^^}. Setting target for ${W}make${Z} to ${W}${ROUTERMDL^^}${Z}."; } unset OLD_ROUTERMDL # Check whether this is the asuswrt-merlin source directory ... [[ -f "$BASEDIR/README-merlin.txt" ]] || fatal help "$BASEDIR is expected to contain a file ${W}README-merlin.txt${Z}." check_runtime_requirements tell_missing_packages # Did the user ask to install prerequisites instead? [[ -n "$INSTALL_PREREQ" ]] && install_required_packages "${PREREQ_MISSING[*]}" ((PREREQ_MISSING_NUM > 0)) && fatal "can't continue because of missing packages.\n\nPlease use:\n\t${W}${0##*/} --prereq $ROUTERMDL${Z}\nor:\n\t${W}$APTI${Z} ${PREREQ_MISSING[*]}\nto install missing packages." if [[ -n "$INTERACTIVE" ]]; then if [[ -n "$INSTALL_PREREQ" ]]; then echo -ne "\nDo you want to proceed with the firmware build now? ([${W}y${Z}]es/[${W}N${Z}]o)" read -p " " ans [[ $ans =~ ^[Yy]$ ]] || { echo -e "\n${Y}INFO:${Z} aborted by user."; exit 0; } fi fi # Special steps required on Ubuntu newer than or equal to 13.10 if [[ -n "$UBUNTU_EXTRAS" ]]; then echo -e "${Y}NOTE:${Z} Attempting extra fixes for Ubuntu >= 13.10 and similar" # fix neon missing proxy.h [[ -f "/usr/include/proxy.h" ]] || fatal "expected to find proxy.h from package libproxy-dev in /usr/include. It's not there, though." cp /usr/include/proxy.h "$BASEDIR/release/src/router/neon/" || fatal "failed to copy proxy.h into source tree" # fix broken configure script for libdaemon ( cd "$BASEDIR/release/src/router/libdaemon" && aclocal ) || fatal "failed to fix broken configure script for libdaemon" # fix broken configure script for libxml2 ( cd "$BASEDIR/release/src/router/libxml2" && sed -i.BAK-build-image s/^AM_C_PROTOTYPES/#AM_C_PROTOTYPES/g configure.in && aclocal ) || fatal "failed to fix broken configure script for libxml2" function_exists hook_ubuntu_extras && hook_ubuntu_extras fi # Clean up leftovers echo -e "${Y}NOTE:${Z} removing excess files" rm -f "$BASEDIR/release/src/router/".#preconfigure* # Prepare the PATH environment variable and fix up hardcoded paths contained in certain files) [[ "$ARCH" == "ARM" ]] && USE_SUDO='' # no global paths needed, so no symlinking needed function_exists hook_pre_fixups && hook_pre_fixups if [[ -n "$USE_SUDO" ]]; then echo -e "${Y}NOTE:${Z} making toolchain available in /opt" sudo ln -sf "$BASEDIR/tools/brcm" /opt/brcm export TOOLCHAIN="/opt/brcm/hndtools-mipsel-uclibc" else # Cheapo version of "installing" the toolchain to /opt/brcm case "$ARCH" in ARM) # Instead of installing, we override some of the paths used in the build process export TOOLCHAIN="$BASEDIR/release/$MKBASE/toolchains/hndtools-arm-linux-2.6.36-uclibc-4.5.3" # used in preconfigure scripts ;; MIPS) echo -e "${Y}NOTE:${Z} looking for files in which to fix hardcoded path to toolchain" grep -P '=\s*/opt/brcm/hndtools-mipsel-uclibc/' $(find "$BASEDIR/release/" -type f -iname '*makefile' -o -name defconfig) 2>/dev/null|cut -d : -f 1|sort -u|while read fname; do [[ -e "${fname}.BAK-build-image" ]] && { echo -e "${W}INFO:${Z} skipping fixup of ${fname} (prior run detected)"; continue; } [[ "x${fname//.BAK-build-image/}" == "x$fname" ]] || continue # skip backup files echo -e "Rebasing paths in: ${G}${fname}${Z}" sed -i.BAK-build-image 's#/opt/brcm/hndtools-mipsel-uclibc/#'"$BASEDIR/tools/brcm/hndtools-mipsel-uclibc/"'#g' "$fname" && grep --color=auto "$BASEDIR/tools/brcm/hndtools-mipsel-uclibc/" "$fname" done # Instead of installing, we override some of the paths used in the build process export TOOLCHAIN="$BASEDIR/tools/brcm/hndtools-mipsel-uclibc" # used in preconfigure scripts ;; esac # Set the trap to also revert the changes we made trap cleanup_and_revert EXIT fi function_exists hook_post_fixups && hook_post_fixups # Add the toolchain into the PATH export PATH="$PATH:$TOOLCHAIN/bin" # Check that the relevant tools execute from there echo -e "${Y}NOTE:${Z} invoking each of the required programs to detect possible issues" for i in $TOOLCHAIN_BINARIES; do $i --version > /dev/null 2>&1 || \ { function_exists hook_suppress_missing_bin && hook_suppress_missing_bin "$i" || true; [[ $? -eq 0 ]] || fatal "failed to execute $i"; } done # Do the make function_exists hook_pre_clean && hook_pre_clean "$BASEDIR/release/$MKBASE" echo -e "${Y}NOTE:${Z} running ${W}make -C release/$MKBASE clean${Z}" make -C "$BASEDIR/release/$MKBASE" "TOOLCHAIN=$TOOLCHAIN" clean > /dev/null 2>&1 function_exists hook_pre_make && hook_pre_make "$BASEDIR/release/$MKBASE" set -e echo -e "${Y}NOTE:${Z} running ${W}make -C release/$MKBASE ${ROUTERMDL,,}${Z}" make -C "$BASEDIR/release/$MKBASE" "TOOLCHAIN=$TOOLCHAIN" ${ROUTERMDL,,} function_exists hook_post_make && hook_post_make "$BASEDIR/release/$MKBASE" echo "Your image can be found here:" (cd "$BASEDIR" && for i in release/$MKBASE/image/*.trx; do echo -e "\t${G}$i${Z}"; done)