[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250224-vivid-merry-oriole-f0cb8a@l-nschier-z2>
Date: Mon, 24 Feb 2025 15:25:33 +0100
From: Nicolas Schier <n.schier@....de>
To: Masahiro Yamada <masahiroy@...nel.org>
Cc: linux-kbuild@...r.kernel.org, Nathan Chancellor <nathan@...nel.org>,
linux-kernel@...r.kernel.org
Subject: Re: [PATCH] kbuild: add Kbuild bash completion
On Sat, Feb 08, 2025 at 04:31:31AM +0900, Masahiro Yamada wrote:
> Kernel build commands can sometimes be long, particularly when
> cross-compiling, making them tedious to type and prone to mistypes.
>
> This commit introduces bash completion support for common variables
> and targets in Kbuild.
>
> For installation instructions, please refer to the documentation in
> Documentation/kbuild/bash-completion.rst.
>
> The following examples demonstrate how this saves typing.
>
> [Example 1] a long command line for cross-compiling
>
> $ make A<TAB>
> -> completes 'A' to 'ARCH='
>
> $ make ARCH=<TAB>
> -> displays all supported architectures
>
> $ make ARCH=arm64 CR<TAB>
> -> completes 'CR' to 'CROSS_COMPILE='
>
> $ make ARCH=arm64 CROSS_COMPILE=<TAB>
> -> displays installed toolchains
>
> $ make ARCH=arm64 CROSS_COMPILE=aa<TAB>
> -> completes 'CROSS_COMPILE=aa' to 'CROSS_COMPILE=aarch64-linux-gnu-'
>
> $ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- def<TAB>
> -> completes 'def' to 'defconfig'
>
> [Example 2] a single build target
>
> $ make f<TAB>
> -> completes 'f' to 'fs/'
>
> $ make fs/<TAB>
> -> displays objects and sub-directories in fs/
>
> $ make fs/xf<TAB>
> -> completes 'fs/xf' to 'fs/xfs/'
>
> $ make fs/xfs/l<TAB>
> -> completes 'fs/xfs/l' to 'fs/xfs/libxfs/xfs_'
>
> $ make fs/xfs/libxfs/xfs_g<TAB>
> -> completes 'fs/xfs/libxfs/xfs_g' to 'fs/xfs/libxfs/xfs_group.o'
>
> This does not aim to provide a complete list of variables and targets,
> as there are too many. However, it covers variables and targets used
> in common scenarios, and I hope this is useful enough.
>
> Signed-off-by: Masahiro Yamada <masahiroy@...nel.org>
> ---
Thanks! I have been testing this a few days and find it quite handy;
especially I do like the toolchain completion!
>
> Documentation/kbuild/bash-completion.rst | 65 ++++
> Documentation/kbuild/index.rst | 2 +
> MAINTAINERS | 1 +
> scripts/bash-completion/make | 451 +++++++++++++++++++++++
> 4 files changed, 519 insertions(+)
> create mode 100644 Documentation/kbuild/bash-completion.rst
> create mode 100644 scripts/bash-completion/make
>
> diff --git a/Documentation/kbuild/bash-completion.rst b/Documentation/kbuild/bash-completion.rst
> new file mode 100644
> index 000000000000..2b52dbcd0933
> --- /dev/null
> +++ b/Documentation/kbuild/bash-completion.rst
> @@ -0,0 +1,65 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +==========================
> +Bash completion for Kbuild
> +==========================
> +
> +The kernel build system is written using Makefiles, and Bash completion
> +for the `make` command is available through the `bash-completion`_ project.
> +
> +However, the Makefiles for the kernel build are complex. The generic completion
> +rules for the `make` command do not provide meaningful suggestions for the
> +kernel build system, except for the options of the `make` command itself.
> +
> +To enhance completion for various variables and targets, the kernel source
> +includes its own completion script at `scripts/bash-completion/make`.
> +
> +This script provides additional completions when working within the kernel tree.
> +Outside the kernel tree, it defaults to the generic completion rules for the
> +`make` command.
> +
> +Prerequisites
> +=============
> +
> +The script relies on helper functions provided by `bash-completion`_ project.
> +Please ensure it is installed on your system. On most distributions, you can
> +install the `bash-completion` package through the standard package manager.
> +
> +How to use
> +==========
> +
> +You can source the script directly::
> +
> + $ source scripts/bash-completion/make
> +
> +Or, you can copy it into the search path for Bash completion scripts.
> +For example::
> +
> + $ mkdir -p ~/.local/share/bash-completion/completions
> + $ cp scripts/bash-completion/make ~/.local/share/bash-completion/completions/
> +
> +Details
> +=======
> +
> +The additional completion for Kbuild is enabled in the following cases:
> +
> + - You are in the root directory of the kernel source.
> + - You are in the top-level build directory created by the O= option
> + (checked via the `source` symlink pointing to the kernel source).
> + - The -C make option specifies the kernel source or build directory.
> + - The -f make option specifies a file in the kernel source or build directory.
> +
> +If none of the above are met, it falls back to the generic completion rules.
> +
> +The completion supports:
> +
> + - Commonly used targets, such as `all`, `menuconfig`, `dtbs`, etc.
> + - Make (or environment) variables, such as `ARCH`, `LLVM`, etc.
> + - Single-target builds (`foo/bar/baz.o`)
> + - Configuration files (`*_defconfig` and `*.config`)
> +
> +Some variables offer intelligent behavior. For instance, `CROSS_COMPILE=`
> +followed by a TAB displays installed toolchains. The list of defconfig files
> +shown depends on the value of the `ARCH=` variable.
> +
> +.. _bash-completion: https://github.com/scop/bash-completion/
> diff --git a/Documentation/kbuild/index.rst b/Documentation/kbuild/index.rst
> index e82af05cd652..3731ab22bfe7 100644
> --- a/Documentation/kbuild/index.rst
> +++ b/Documentation/kbuild/index.rst
> @@ -23,6 +23,8 @@ Kernel Build System
> llvm
> gendwarfksyms
>
> + bash-completion
> +
> .. only:: subproject and html
>
> Indices
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 896a307fa065..cca379fbeb4f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12566,6 +12566,7 @@ F: Makefile
> F: scripts/*vmlinux*
> F: scripts/Kbuild*
> F: scripts/Makefile*
> +F: scripts/bash-completion/
> F: scripts/basic/
> F: scripts/clang-tools/
> F: scripts/dummy-tools/
> diff --git a/scripts/bash-completion/make b/scripts/bash-completion/make
> new file mode 100644
> index 000000000000..d06e642ddcf7
> --- /dev/null
> +++ b/scripts/bash-completion/make
> @@ -0,0 +1,451 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +# bash completion for GNU make with kbuild extension -*- shell-script -*-
> +
> +# Load the default completion script for make. It is typically located at
> +# /usr/share/bash-completion/completions/make, but we do not rely on it.
> +__kbuild_load_default_make_completion()
> +{
> + local -a dirs=("${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions")
> + local ifs=$IFS IFS=: dir compfile this_dir
> +
> + for dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
> + dirs+=("$dir"/bash-completion/completions)
> + done
> + IFS=$ifs
> +
> + this_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
> +
> + for dir in "${dirs[@]}"; do
> + if [[ ! -d ${dir} || ${dir} = "${this_dir}" ]]; then
> + continue
> + fi
> +
> + for compfile in make make.bash _make; do
> + compfile=$dir/$compfile
> + # Avoid trying to source dirs; https://bugzilla.redhat.com/903540
> + if [[ -f ${compfile} ]] && . "${compfile}" &>/dev/null; then
> +
> + __kbuild_default_make_completion=$(
> + # shellcheck disable=SC2046 # word splitting is the point here
> + set -- $(complete -p make)
> +
> + while [[ $# -gt 1 && "$1" != -F ]]; do
> + shift
> + done
> +
> + if [[ "$1" = -F ]]; then
> + echo "$2"
> + fi
> + )
> +
> + return
> + fi
> + done
> + done
> +}
> +
> +__kbuild_load_default_make_completion
> +
> +__kbuild_handle_variable()
> +{
> + local var=${1%%=*}
> + local cur=${cur#"${var}"=}
> + local srctree=$2
> + local keywords=()
> +
> + case $var in
> + ARCH)
> + # sub-directories under arch/
> + keywords+=($(find "${srctree}/arch" -mindepth 1 -maxdepth 1 -type d -printf '%P\n'))
> + # architectures hard-coded in the top Makefile
> + keywords+=(i386 x86_64 sparc32 sparc64 parisc64)
> + ;;
> + CROSS_COMPILE)
> + # toolchains with a full path
> + local cross_compile=()
> + local c c2
> + _filedir
> +
> + for c in "${COMPREPLY[@]}"; do
> + # eval for tilde expansion
> + # suppress error, as this fails when it contains a space
> + eval "c2=${c}" 2>/dev/null || continue
> + if [[ ${c} == *-elfedit && ! -d ${c2} && -x ${c2} ]]; then
> + cross_compile+=("${c%elfedit}")
> + fi
> + done
> +
> + # toolchains in the PATH environment
> + while read -r c; do
> + if [[ ${c} == *-elfedit ]]; then
> + keywords+=("${c%elfedit}")
> + fi
> + done < <(compgen -c)
> +
> + COMPREPLY=()
> + _filedir -d
> +
> + # Add cross_compile directly without passing it to compgen.
> + # Otherwise, toolchain paths with a tilde do not work.
> + # e.g.)
> + # CROSS_COMPILE=~/0day/gcc-14.2.0-nolibc/aarch64-linux/bin/aarch64-linux-
> + COMPREPLY+=("${cross_compile[@]}")
> + ;;
> + LLVM)
> + # LLVM=1 uses the default 'clang' etc.
> + keywords+=(1)
> +
> + # suffix for a particular version. LLVM=-18 uses 'clang-18' etc.
> + while read -r c; do
> + if [[ ${c} == clang-[0-9]* ]]; then
> + keywords+=("${c#clang}")
> + fi
> + done < <(compgen -c)
> +
> + # directory path to LLVM toolchains
> + _filedir -d
> + ;;
> + KCONFIG_ALLCONFIG)
> + # KCONFIG_ALLCONFIG=1 selects the default fragment
> + keywords+=(1)
> + # or the path to a fragment file
> + _filedir
> + ;;
> + C | KBUILD_CHECKSRC)
> + keywords+=(1 2)
> + ;;
> + V | KBUILD_VERBOSE)
> + keywords+=({,1}{,2})
> + ;;
> + W | KBUILD_EXTRA_WARN)
> + keywords+=({,1}{,2}{,3}{,c}{,e})
> + ;;
> + KBUILD_ABS_SRCTREE | KBUILD_MODPOST_NOFINAL | KBUILD_MODPOST_WARN | \
> + CLIPPY | KBUILD_CLIPPY | KCONFIG_NOSILENTUPDATE | \
> + KCONFIG_OVERWRITECONFIG | KCONFIG_WARN_UNKNOWN_SYMBOL | \
> + KCONFIG_WERROR )
> + keywords+=(1)
> + ;;
> + INSTALL_MOD_STRIP)
> + keywords+=(1 --strip-debug --strip-unneeded)
> + ;;
> + O | KBUILD_OUTPUT | M | KBUILD_EXTMOD | MO | KBUILD_EXTMOD_OUTPUT | *_PATH)
> + # variables that take a directory.
> + _filedir -d
Would it make sense to temporarily switch cwd to ${srctree} to allow
completion of KBUILD_OUTPUT and friends with relative paths from
${srctree}?
A simple approach (probably with some bad side-effects), works for me:
local opwd=$OLDPWD
cd ${srctree} && _filedir -d && cd $OLDPWD
OLDPWD=$opwd
But this is quite a step further, if there is no clean solution right
now, I'd prefer leaving it as it is for now.
> + return
> + ;;
> + KBUILD_EXTRA_SYMBOL | KBUILD_KCONFIG | KCONFIG_CONFIG)
> + # variables that take a file.
> + _filedir
> + return
> + esac
> +
> + COMPREPLY+=($(compgen -W "${keywords[*]}" -- "${cur}"))
> +}
> +
> +# Check the -C, -f options and 'source' symlink. Return the source tree we are
> +# working in.
> +__kbuild_get_srctree()
> +{
> + local words=("$@")
> + local cwd makef_dir
> +
> + # see if a path was specified with -C/--directory
> + for ((i = 1; i < ${#words[@]}; i++)); do
indent ^^: spaces instead of tab
> + if [[ ${words[i]} == -@(C|-directory) ]]; then
> + # eval for tilde expansion.
> + # suppress error, as this fails when it contains a space
> + eval "cwd=${words[i + 1]}" 2>/dev/null
> + break
> + fi
> + done
indent ^: spaces instead of tab
> +
> + if [[ -z ${cwd} ]]; then
> + cwd=.
> + fi
> +
> + # see if a Makefile was specified with -f/--file/--makefile
> + for ((i = 1; i < ${#words[@]}; i++)); do
indent ^^: spaces instead of tab
> + if [[ ${words[i]} == -@(f|-?(make)file) ]]; then
(I am really impressed: everytime I am reviewing one of your new shell
scripts, I learn something new. TIL: '-@()' and '?()'.)
> + # eval for tilde expansion
> + # suppress error, as this fails when it contains a space
> + eval "makef_dir=${words[i + 1]%/*}" 2>/dev/null
> + break
> + fi
> + done
indent ^: spaces instead of tab
> +
> + if [ -z "${makef_dir}" ]; then
> + makef_dir=${cwd}
> + elif [[ ${makef_dir} != /* ]]; then
> + makef_dir=${cwd}/${makef_dir}
> + fi
> +
> + # If ${makef_dir} is a build directory created by the O= option, there
> + # is a symbolic link 'source', which points to the kernel source tree.
> + if [[ -L ${makef_dir}/source ]]; then
> + makef_dir=$(readlink "${makef_dir}/source")
> + fi
> +
> + echo "${makef_dir}"
> +}
> +
> +# Get SRCARCH to do a little more clever things
> +__kbuild_get_srcarch()
> +{
> + local words=("$@")
> + local arch srcarch uname_m
> +
> + # see if ARCH= is explicitly specified
> + for ((i = 1; i < ${#words[@]}; i++)); do
indent ^^: spaces instead of tab
> + if [[ ${words[i]} == ARCH=* ]]; then
> + arch=${words[i]#ARCH=}
> + break
> + fi
> + done
indent ^: spaces instead of tab
> +
> + # If ARCH= is not specified, check the build marchine's architecture
> + if [[ -z ${arch} ]]; then
> + uname_m=$(uname -m)
> +
> + # shellcheck disable=SC2209 # 'sh' is SuperH, not a shell command
> + case ${uname_m} in
> + arm64 | aarch64*) arch=arm64 ;;
> + arm* | sa110) arch=arm ;;
> + i?86 | x86_64) arch=x86 ;;
> + loongarch*) arch=loongarch ;;
> + mips*) arch=mips ;;
> + ppc*) arch=powerpc ;;
> + riscv*) arch=riscv ;;
> + s390x) arch=s390 ;;
> + sh[234]*) arch=sh ;;
> + sun4u) arch=sparc64 ;;
> + *) arch=${uname_m} ;;
> + esac
> + fi
> +
> + case ${arch} in
> + parisc64) srcarch=parisc ;;
> + sparc32 | sparc64) srcarch=sparc ;;
> + i386 | x86_64) srcarch=x86 ;;
> + *) srcarch=${arch} ;;
> + esac
> +
> + echo "$srcarch"
> +}
> +
> +# small Makefile to parse obj-* syntax
> +__kbuild_tmp_makefile()
> +{
> +cat <<'EOF'
> +.PHONY: __default
> +__default:
> + $(foreach m,$(obj-y) $(obj-m) $(obj-),$(foreach s, -objs -y -m -,$($(m:%.o=%$s))) $(m))
> +EOF
> +echo "include ${1}"
> +}
> +
> +_make_for_kbuild ()
> +{
> + # shellcheck disable=SC2034 # these are set by _init_completion
> + local cur prev words cword split
> + _init_completion -s || return
> +
> + local srctree
> + srctree=$(__kbuild_get_srctree "${words[@]}")
> +
> + # If 'kernel' and 'Documentation' directories are found, we assume this
> + # is a kernel tree. Otherwise, we fall back to the generic rule provided
> + # by the bash-completion project.
> + if [[ ! -d ${srctree}/kernel || ! -d ${srctree}/Documentation ]]; then
> + if [ -n "${__kbuild_default_make_completion}" ]; then
> + "${__kbuild_default_make_completion}" "$@"
> + fi
> + return
> + fi
> +
> + # make options with a parameter (copied from the bash-completion project)
> + case ${prev} in
> + --file | --makefile | --old-file | --assume-old | --what-if | --new-file | \
indent ^: spaces instead of tab
> + --assume-new | -!(-*)[foW])
> + _filedir
> + return
> + ;;
> + --include-dir | --directory | -!(-*)[ICm])
> + _filedir -d
> + return
> + ;;
> + -!(-*)E)
> + COMPREPLY=($(compgen -v -- "$cur"))
> + return
> + ;;
> + --eval | -!(-*)[DVx])
> + return
> + ;;
> + --jobs | -!(-*)j)
> + COMPREPLY=($(compgen -W "{1..$(($(_ncpus) * 2))}" -- "$cur"))
> + return
> + ;;
> + esac
> +
> + local keywords=()
> +
> + case ${cur} in
> + -*)
> + # make options (copied from the bash-completion project)
> + local opts
> + opts="$(_parse_help "$1")"
> + COMPREPLY=($(compgen -W "${opts:-$(_parse_usage "$1")}" -- "$cur"))
> + if [[ ${COMPREPLY-} == *= ]]; then
> + compopt -o nospace
> + fi
> + return
> + ;;
> + *=*)
> + __kbuild_handle_variable "${cur}" "${srctree}"
> + return
> + ;;
> + KBUILD_*)
> + # There are many variables prefixed with 'KBUILD_'.
> + # Display them only when 'KBUILD_' is entered.
> + # shellcheck disable=SC2191 # '=' is appended for variables
> + keywords+=(
> + KBUILD_{CHECKSRC,EXTMOD,EXTMOD_OUTPUT,VERBOSE,EXTRA_WARN,CLIPPY}=
> + KBUILD_BUILD_{USER,HOST,TIMESTAMP}=
> + KBUILD_MODPOST_{NOFINAL,WARN}=
> + KBUILD_{ABS_SRCTREE,EXTRA_SYMBOLS,KCONFIG}=
Did you leave-out KBUILD_OUTPUT by intention? I think it would be nice
to complete it here, too.
I am unsure, if I like the mix of quoted and unquoted use of variables.
But as they all look correct to me, this is just a question of personal
style.
Thanks, I really appreciate this kbuild bash-completion.
Reviewed-by: Nicolas Schier <n.schier@....de>
Tested-by: Nicolas Schier <n.schier@....de>
Kind regards,
Nicolas
Powered by blists - more mailing lists