#!/bin/bash
#####################################################################################
WHAT="Automatic kernel Makefile generation"
WHO="Copyright, Jerry Cooperstein, coop@linuxfoundation.org, 2/2003 - 1/2020"
LICENSE="GPLv2"

VERSION=2.0

OBJS=""   # list of kernel modules (.o files)
K_S=""    # list of kernel modules (.c files)
U_S=""    # list of userland programs (.c files)
U_X=""    # list of userland programs (executables)
ALL=""    # list of all targets

CFLAGS_U_X="-O2 -Wall -pthread"    # compile flags for user programs

#####################################################################################
usage() {
    echo "Usage: ${0##*/} [kernel-headers] [arch] [cross-compiler]"
    echo "   or: [KROOT=<dir>] [ARCH=<arch>] [CROSS_COMPILE=<compiler>] ${0##*/}"
    echo
    echo "   examples:"
    echo "      $ genmake"
    echo "      $ genmake \$HOME/linux-4.17 arm-linux-gnueabihf genmake"
    echo "      $ KROOT=\$HOME/linux-4.17 CROSS_COMPILE=arm-linux-gnueabihf genmake"
    echo "      $ KROOT=none genmake   # Forces compiling for native kernel"
    exit 0
}

TEST=${TEST:+echo}

#####################################################################################
while [[ $# -gt 0 ]] ; do
    case "$1" in
        -V|--version) echo "Version v$VERSION"; exit 0;;
        -h|--help|-*) usage;;
        *) break;;
    esac
    shift
done

#####################################################################################
if [[ -f Makefile ]] ; then
    if grep -q ${0##*/} Makefile ; then
        echo "I: Refreshing Makefile"
    elif [[ -n $FORCE ]] ; then
        echo "W: Overwriting a non-${0##*/} generated Makefile"
    else
        echo "E: Cowardly refusing to overwrite a non-${0##*/} generated Makefile"
        exit 1
    fi
else
    echo "I: Generating Makefile"
    touch Makefile
fi

#####################################################################################
# Take from $KROOT or the first command line argument
# Alternatively read it from an existing Makefile
# Otherwise set the kernel source to the running one
if [[ -z $KROOT && -d $1 ]] ; then
    KROOT=$1; shift
fi
[[ -n $KROOT ]] || KROOT=$(awk -F= '/KROOT *=/ {print $2}' Makefile | sed -e 's/^ //')
[[ -z $KROOT || $KROOT = - || $KROOT = none ]] && KROOT=/lib/modules/$(uname -r)/build

# abort if the source is not present
if [[ ! -d $KROOT || ! -f $KROOT/Makefile ]] ; then
    echo "E: kernel source directory $KROOT does not exist or has no Makefile"
    exit 1
fi

#####################################################################################
# Are there additional flags?
if [[ -n $KINCS ]] ; then
    CFLAGS_U_X="$CFLAGS_U_X -I$KROOT/include"
fi

#####################################################################################
# Skip empty directories
if [[ -z $(find . -maxdepth 1 -name "*.c") ]] ; then
    echo "W: No need to make Makefile: no source code"
    exit 0
fi

#####################################################################################
# Construct lists of kernel and user sources and targets
for NAME in *.c ; do
    [[ $NAME =~ .*\.mod\.c ]] && continue
    [[ $NAME =~ .*\.k?o ]] && continue
    # exclude files with NOMAKE or .mod.c files
    if grep -q NOMAKE "$NAME" || grep -q vermagic "$NAME" ; then
        echo "W: $NAME is being skipped, it is not a module or program"
    else
        if grep -q '<linux/module.h>' "$NAME" ; then
            OBJS="$OBJS ${NAME/.c/.o}"
            K_S="$K_S $NAME"
        else
            U_X="$U_X ${NAME/.c/}"
            U_S="$U_S $NAME"
        fi
    fi
done

CLEANSTUFF="$U_X"

#####################################################################################
# Maybe there are no kernel modules
if [[ -n $OBJS ]] ; then
    echo "KROOT=$KROOT"
    CLEANSTUFF="$CLEANSTUFF Module.symvers modules.order"
else
    unset KROOT
fi

#####################################################################################
# Figure out architecture and cross compiler
if [[ -z $ARCH && ! $1 =~ .*-.* ]] ; then
    ARCH=$1; shift
fi
if [[ -z $ARCH && -n $KROOT && -f $KROOT/.config ]] ; then
    ARCH=$(awk '/^# Linux\// {print $2}' "$KROOT/.config" | sed 's|Linux/||')
fi
if [[ $ARCH = x86 && $(uname -m) = x86_64 ]] ; then
    echo "Compiling natively for x86_64"
    unset ARCH
else
    [[ -n $ARCH ]] || ARCH=$(awk '/^ARCH = / {print $3}' Makefile)
    [[ -z $ARCH ]] || echo "ARCH=$ARCH"

    #################################################################################
    if [[ -z $CROSS_COMPILE && -n $1 ]] ; then
        CROSS_COMPILE="${1%-}-"; shift
    fi
    if [[ -z $CROSS_COMPILE ]] ; then
        CROSS_COMPILE=$(awk '/^CROSS_COMPILE = / {print $3}' Makefile)
    fi

    #################################################################################
    GCC=$(command -v -- "${CROSS_COMPILE%-}-gcc" 2>/dev/null)
    if [[ -n $CROSS_COMPILE ]] ; then
        if [[ -n $GCC ]] ; then
            echo "I: Using $GCC"
        else
            echo "W: Invalid CROSS_COMPILE=$CROSS_COMPILE, will revert to defaults"
            unset CROSS_COMPILE
        fi
    fi

    #################################################################################
    if grep -q CONFIG_64BIT=y "$KROOT/.config" ; then
        if grep -q CONFIG_CPU_LITTLE_ENDIAN=y "$KROOT/.config" ; then
            [[ -z $CROSS_COMPILE && $ARCH = mips ]] && CROSS_COMPILE=mips64el-linux-gnu-
        else
            [[ -z $CROSS_COMPILE && $ARCH = mips ]] && CROSS_COMPILE=mips64-linux-gnu-
        fi
    elif grep -q CONFIG_PPC64=y "$KROOT/.config" ; then
        [[ -z $CROSS_COMPILE && $ARCH = powerpc ]] && CROSS_COMPILE=powerpc64-linux-gnu-
    else
        [[ -z $CROSS_COMPILE && $ARCH = arm ]] && CROSS_COMPILE=arm-linux-gnueabihf-
        [[ -z $CROSS_COMPILE && $ARCH = arm64 ]] && CROSS_COMPILE=aarch64-linux-gnu-
        [[ -z $CROSS_COMPILE && $ARCH = mips ]] && CROSS_COMPILE=mips-linux-gnu-
        [[ -z $CROSS_COMPILE && $ARCH = powerpc ]] && CROSS_COMPILE=powerpc-linux-gnu-
    fi
    [[ -z $CROSS_COMPILE ]] || echo "CROSS_COMPILE=${CROSS_COMPILE%-}-"
fi

#####################################################################################
# Get ALL the targets
[[ -z $U_X ]]  || ALL=$ALL" userprogs"
[[ -z $OBJS ]] || ALL=$ALL" modules"

#####################################################################################
# We're done preparing, lets build the makefile finally!

(   printf '### %-72s ###\n' "$WHAT by '${0##*/}' script"
    printf '### %-72s ###\n' "$WHO"
    printf '### %-72s ###\n' "License: $LICENSE"
) > Makefile

#####################################################################################
# Modules list
if [[ -n $K_S ]] ; then
    echo "obj-m =$OBJS"
    echo -e "\nobj-m +=$OBJS\n" >>Makefile
    if [[ -n $KROOT ]] ; then
        echo "KROOT = $KROOT" >>Makefile
    fi
fi

#####################################################################################
# Output ARCH and CROSS_COMPILER
if [[ -n $ARCH ]] ; then
    echo -e "\nARCH = $ARCH" >> Makefile
    KOPTS="$KOPTS ARCH=\$(ARCH)"
    if [[ -n $CROSS_COMPILE ]] ; then
        KOPTS="$KOPTS CROSS_COMPILE=\$(CROSS_COMPILE)"
        cat <<-END >>Makefile
		CROSS_COMPILE = ${CROSS_COMPILE%-}-
		CC = \$(CROSS_COMPILE)gcc
		END
    fi
fi

#####################################################################################
# Default rules
cat <<END >>Makefile

all allofit:$ALL

END

#####################################################################################
# kernel rules
if [[ -n $K_S ]] ; then
    cat <<END >>Makefile
modules modules_install clean::
	\$(MAKE)$KOPTS -C \$(KROOT) M=\$(shell pwd) \$@

END
fi

#####################################################################################
MAKEOPTS="CC=\"\$(CC)\" CFLAGS=\"$CFLAGS_U_X\""
[[ -z $LDLIBS ]] || MAKEOPTS="$MAKEOPTS LDLIBS=\"$LDLIBS\""
[[ -z $CPPFLAGS ]] || MAKEOPTS="$MAKEOPTS CPPFLAGS=\"$CPPFLAGS\""

#####################################################################################
# Userspace utilities rules
if [[ -n $U_X ]] ; then
    echo "userprogs =$U_X"
    cat <<END >>Makefile
userprogs:
	\$(MAKE) $MAKEOPTS$U_X

END
fi

#####################################################################################
# Clean rules
cat <<END >>Makefile
clean::
	rm -rf $CLEANSTUFF

END
######################################################################################
