Paso Uno

One foot in front of the other

View on GitHub Home About
Sun, 07 Jul 2024 21:35:00 +0000

Where does your local Linux Kernel build get its kernel release version string from?

DISCLAIMER This post is not intended to give an understanding of the Linux Kernel build (Kbuild) mechanism or Makefile.
For knowing more about the same head over to here.

On building your local Linux Kernel source, the build gets a release version string which can be viewed by making the generic target kernelrelease from the source base directory: $ make kernelrelease You can also read the same from include/config/kernel.release if this file was created by any of the target builds like all, vmlinux, modules, etc.
In this post let us take a look at how this kernel release version string is formed.
kernelrelease is a generic target for the Makefile at the base/root of your Linux Kernel source. $ make help from the source base directory will give you the following help output about this generic target:
make help output

configurators

You need to make any one of the configurators (*config configuration targets) like $ make {old[def]|…}config before $ make -s kernelrelease can work. These configurators make sure that a ./.config file is written to the source base directory. The .config file contains all the CONFIG_* options that were selected and the unselected ones as comments. In addition to the .config file the configurators also create include/config/auto.conf and include/generated/autoconf.h files. The former contains just the CONFIG options you have selected to be built-in (obj-y) or made a loadable module (obj-m) and the CONFIG options with values. The latter contains these CONFIG options converted into #define directives. The include/config/auto.conf file is mandatory to execute $ make kernelrelease.

Now we can peek into ./Makefile, the Makefile at the base of the source.



The Makefile

This Makefile sets and exports many variables, defines some basic make rules starting from line 2 to line 208. These variables/rules include the default target __all, the absolute path for the source tree abs_srctree, the absolute path of the output directory abs_objtree, make flags, the make/build verbosity KBUILD_VERBOSE, the make options, the external modules to build KBUILD_EXTMOD etc.

beginning

output directory
This Makefile doesn’t prefer to show Entering directory … text in its output (i.e. prefers to be invoked with the –no-print-directory option) except in the case where the make invocation directory (PWD) and the output directory (abs_objtree) are different. This preference will result in an immediate recursive call with the –no-print-directory option set after the user issues the first make at the cmd line and this is achieved by the __sub-make rule/target. And if the make invocation directory (PWD) and the output directory (abs_objtree) are not the same we can end up recursing twice into the __sub-make rule/target. (once with –no-print-directory NOT set to show the “Entering directory …” message and then with –no-print-directory set to start the make process and avoid printing “Entering directory …” each time make recurses into the Makefile in a subdirectory). The __sub-make rule is controlled by the need-sub-make variable/flag.
__sub-make
The __sub-make rule puts the variable definitions from line 2 to line 208 at the risk of being executed more than once. This is avoided by defining and exporting sub_make_done at the end of first invocation of the make. The value of sub_make_done variable is checked at line 45, here, before the variable definition are done.

kernelrelease rule

kernelrelease rule
It is this make rule with the target kernelrelease, that gets assigned to the variable PHONY, which eventually is declared as a PHONY target that is responsible for displaying the kernel release version string. The filechk_kernel.release variable in the recipe is set at 2 different places higher up in the Makefile.

filechk_kernel.release variable

The first location, from the bottom-up, where filechk_kernel.release is defined is under the branch for building a specific external module. (checkout ‘Building External Modules’ and line 139 - line 156 of the Makefile)

KBUILD_EXTMOD

The second is within the branch where make was not invoked for a specific external module: !KBUILD_EXTMOD
filechk_kernel.release
So it is the latter definition of filechk_kernel.release that is in effect while making the generic target kernelrelease.

origin function

Here you can see that ifeq ($(origin KERNELRELEASE), file) dictates how filechk_kernel.release is set. origin is a GNU make text processing function that tells you where its argument text/variable came from. origin returns a string ‘file’ if the argument text/variable was defined in a makefile.

There is also a rule for creating a file include/config/kernel.release in the following lines. It has an interesting prerequiste FORCE and a call to function filechk with arguments kernel.release. Let us explore these a bit.

FORCE prerequisite

First, let us see what this FORCE prerequiste means. FORCE prerequisite is a way of making sure that the recipe to make its target is always run whenever make is run without any specific targets or when make is run for that specific rule. This happens because FORCE itself is defined way down below in the Makefile as a rule without any prerequistes or recipe. force
Since the target of this rule is a non-existent file, make assumes it to be updated whenever its rule is run. Thus all targets depending on this target (FORCE) will always have their recipe run. It has the same effect of using .PHONY: include/config/kernel.release and is useful for other versions of make that do not support .PHONY rules. The name FORCE can be replaced with any other name to create this special rule. Refer Rule without Recipes or Prerequisites for more clarity.

filechk function

Now what is left of the rule is the $(call filechk,kernel.release) recipe. The function filechk is defined in the file scripts/Kbuild.include and is available in Makefile as it is included here: kbuild.include
This is the definition of filechk function:
filechk func
The function first checks if FORCE is specified in the list of prerequisites, else a warning is emitted about missing FORCE prerequisite and make continues. Then it creates the directory include/config if it doesn’t exist. This is followed by setting up a trap to remove/cleanup the temp file include/config/.tmp_kernel.release (which we will be creating next) on script EXIT. Then the contents of variable filechk_kernel.release is written to the temp file include/config/.tmp_kernel.release. The script then checks if the file include/config/kernel.release does not exist or if its contents does not match the contents of the temp file include/config/.tmp_kernel.release. In either case it emits a message UPD include/config/kernel.release depending on whether make verbosity was set to 1 or not and makes the temp file the new include/config/kernel.release file.
As you can see this rule will be run only for $ make include/config/kernel.release invocation or for any make invocation that run special rules like .PHONY or FORCE without recipes or prerequisites.

KERNELRELEASE variable

Coming back to the filechk_kernel.release rule, inorder to figure out if $(ORIGIN KERNELRELEASE) evaluates to ‘file’ we need to check if KERNELRELEASE was set somewhere earlier in the Makefile.

kernelrelease var
KERNELRELEASE is read from include/config/kernel.release in the branch for non mixed-build targets. But this file is not generated while making specific generic target kernelrelease as explained here. Hence KERNELRELEASE is exported with a NULL value. Though, there is nothing stopping you from creating this file before running make and setting KERNELRELEASE to whatever is in it during make.

We now know that KERNELRELEASE was exported from this very same Makefile itself and that ifeq ($(origin KERNELRELEASE), file) evaluates to TRUE. Thus the variable filechk_kernel.release is set to the output of invoking the $(srctree)/scripts/setlocalversion script with $(srctree) as the argument.

single-build & mixed-build

Before we proceed further let us digress a bit about single-build and mixed-build. single-mixed-build
single-build is specified only if the make goals, (MAKECMDGOALS), contain at least one single-targets item. single-targets items are *.a, *.ko, *.o etc.

mixed-build is set if the make goals contain more than one config targets and/or if single-targets is specified along with other make goals and/or if the clean-targets is specified along with other make goals and/or if the install & modules_install targets are specified together.

The definitions of these variables can be seen a few lines up: definitions

scripts/setlocalversion

scripts/setlocalversion is the script that is eventually invoked to get the kernelrelease string which is assigned to the filechk_kernel.release variable. This script will try to get the values of the following variables/items if they are set/available:

setlocalversion

KERNELVERSION

KERNELVERSION variable is set and exported in the Makefile at the source base directory. This is how it is formed:
version kernelversion

file_localversion

file_localversion is formed from the contents of localversion* files, if any, in the build and source directories. The collect_files function is invoked to read and get the contents of all the localversion* files.
file_localversion

config_localversion

config_localversion is set to the value provided to the CONFIG_LOCALVERSION config option while running a configurator like menuconfig and generating the .config file. config_localversion

scm_version

This script forms the full scm version string if the CONFIG_LOCALVERSION_AUTO config option is set in the .config file. If this config option is NOT set and LOCALVERSION is also not set, then the script goes for short scm version string. scm_version
Finally let us explore the scm_version function: scm_version_func1
The function sets variables short and no_dirty to true based on the options it was passed. It then does a cd to the source directory the script was invoked with. git rev-parse –show-cdup is run to make sure that we are at the source base directory i.e. to ensure that source directory passed to the script as argument is indeed the actual source base directory. git rev-parse –verify HEAD is then run to make sure that the HEAD is pointing to a valid commit. The version_tag variable is formed by stripping the SUBLEVEL part from the KERNELVERSION if it’s 0 and then prefixing a ‘v’ to the resulting string. scm_version_func2
Next the tag & desc variables are formed. First tag is obtained from the contents of the file_localversion variable, (which was formed by reading localversion* files from the source directory and build directories), after removing any leading ‘-‘. Then the function checks if such a tag exists using git describe –match=$tag and stores the result in desc variable. If a matching tag could not be found in the previous step, the function sets tag to version_tag and checks again. scm_version_func3
If a matching tag was till not found, it implies that we either do not have the tag we are searching for or that we have moved past the tagged commit with later commits. In either case if the short option was set, the function just prints (echo) a “+” and returns. If short option was not set, the function checks if it is the case that we have moved past the tag by later commits and if so, the function prints the commit index (distance) of latest commit from the tagged commit as a 5 chars wide string padded with 0s to left and prefixed with a “-“. The function follows this by printing the first 12 chars from the hash of the HEAD commit (the latest commit itself) prefixed with a “-g”. The function returns from here if the no_dirty option was set.
scm_version_func4
At last, if the no_dirty option was not set, the function prints “-dirty” if we have uncommitted changes in our source tree.

Illustrations

Some kernelrelease string examples.

With CONFIG_LOCALVERSION not set and CONFIG_LOCALVERSION_AUTO turned OFF/ON.
example1
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned OFF/ON.
example2
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned OFF and uncommitted changes.
example3
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned OFF and later commits than the release tag.
example4
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned OFF and later commits than the release tag and uncommitted changes (same output as above).
example4
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned ON and later commits than the release tag.
example5
With CONFIG_LOCALVERSION set to “-asr” and CONFIG_LOCALVERSION_AUTO turned ON and later commits than the release tag and uncommitted changes.
example6

The End

With this we have come to the end of this post!
Hope you had a good time reading.

– Aby

penguin
tags: linux - kernel - v6.7 - makefile - kernelrelease - git - scm - localversion - kconfig - kbuild
this/is/a/code/file/path this is a code keyword this is a generic keyword