⚠️ Макроязык Kconfig
Материал возможно устарел.
Концепция
Основная идея была вдохновлена Make. Мы посмотрели на Make и заметили будто он сочетает два языка в одном. Один язык описывает графы зависимостей, состоящие из целей и предварительных условий. Другой язык является макроязыком для выполнения текстовых подстановок.
Существует чёткое различие между двумя стадиями языка. Например, вы можете написать makefile следующим образом:
APP := foo
SRC := foo.c
CC := gcc
$(APP): $(SRC)
$(CC) -o $(APP) $(SRC)
Макроязык заменяет обращения к переменным их расширенной формой и обрабатывает файл так, как если бы исходный файл выглядел следующим образом:
foo: foo.c
gcc -o foo foo.c
Затем Make анализирует граф зависимостей и определяет, что должно быть обновлено.
Идея в Kconfig довольно похожа - можно описать файл Kconfig следующим образом:
CC := gcc
config CC_HAS_FOO
def_bool $(shell, $(srctree)/scripts/gcc-check-foo.sh $(CC))
Макроязык в Kconfig обработает исходный файл и получится следующий промежуточный результат:
config CC_HAS_FOO
def_bool y
Затем Kconfig переходит к этапу оценки для разрешения взаимозависимости символов. Подробнее про это можно прочитать в документации языка Kconfig.
Переменные
Как и в Make, переменная в Kconfig работает как макропеременная. Макропеременная разворачивается "на месте", чтобы получить текстовую строку, котора я затем может быть развёрнута дальше. Чтобы получить значение переменной, заключите имя переменной в $( )
. Скобки требуются даже для однобуквенных имен переменных; $X
является синтаксической ошибкой. Форма с фигурными скобками, как в ${CC}
, также не поддерживается.
Существует два типа переменных: переменные разворачиваемые "просто" и переменные разворачиваемые рекурсивно.
Переменная разворачиваемая "просто" определяется с использованием оператора присваивания :=
. Ее правая часть разворачивается сразу же при чтении строки из файла Kconfig.
Переменная разворачиваемая рекурсивно определяется с использованием оператора присваивания =
. Ее правая часть просто сохраняется как значение переменной без какого-либо расширения. Вместо этого расширение происходит при использовании этой переменной.
Существует еще один тип оператора присваивания; оператор +=
используется для добавления текста к переменной. Правая часть оператора +=
разворачивается немедленно, если левая часть была изначально определена как простая переменная. В противном слу чае её вычисление откладывается.
Обращение к переменной может принимать параметры в следующей форме:
$(name,arg1,arg2,arg3)
Вы можете рассматривать параметризованное обращение как функцию. (более точно, как "пользовательскую функцию" в отличие от "встроенной функции", рассмотренной ниже).
Полезные функции должны разворачиваться в момент их использования, так как одна и та же функция разворачивается по-разному при передаче различных параметров. Поэтому пользовательская функция определяется с использованием оператора присваивания =
. Параметры упоминаются внутри определения тела функции с помощью $(1)
, $(2)
и так далее.
Фактически, рекурсивно расширяемые переменные и пользовательские функции внутренне идентичны. (Другими словами, "переменная" — это "функция без аргументов".) Когда мы говорим "переменная" в широком смысле, это включает в себя и "пользовательскую функцию".
Встроенные функции
Как и Make, Kconfig предоставляет несколько встроенных функций. Каждая функция принимает определенное количество аргументов.
В Make каждая встроенная функция принимает как минимум один аргумент. В Kconfig разрешается использование нулевого количества аргументов для встроенных функций, таких как $(filename)
, $(lineno)
. Вы можете считать их "встроенными переменными", но это всего лишь вопрос терминологии. Давайте всё же будем называть их "встроенными функциями", чтобы обозначить нативно подд ерживаемую функциональность.
Kconfig currently supports the following built-in functions.
-
$(shell,command)
Функция "shell" принимает один аргумент, который разворачивается и передается в дочерний shell для выполнения. Затем стандартный вывод команды считывается и возвращается в качестве значения функции. Каждая новая строка в выводе заменяется на пробел. Любые повторяющиеся новые строки удаляются. Стандартная ошибка не возвращается, также не возвращается никакой статус завершения программы (exit status).
-
$(info,text)
The "info" функция принимает один аргумент и выводит его в стандартный вывод (stdout). Она вычисляется как пустая строка.
-
$(warning-if,condition,text)
Функция "warning-if" принимает два аргумента. Если часть условия равна "y", то текстовая часть отправляется в stderr. В начале этого текста вставляется имя текущего файла Kconfig и текущей номер строки.
-
$(error-if,condition,text)
Функция "error-if" аналогична функции "warning-if", но немедленно прекращает процесс анализа конфигурации, если условие равно "y".
-
$(filename)
The 'filename' не принимает аргументов, и
$(filename)
разворачивается в имя файла, который анализируется. -
$(lineno)
The 'lineno' не принимает аргументов, и
$(lineno)
разворачивается в номер строки, которая обрабатывается.
Make vs Kconfig
Kconfig использует макроязык, похожий на Make, но с немного отличающимся синтаксисом вызова функций.
Вызов функции в Make выглядит так:
$(func-name arg1,arg2,arg3)
Имя функции и первый аргумент разделяются как минимум одним пробелом. Затем ведущие пробелы удаляются из первого аргумента, в то время как пробелы в других аргументах остаются. Вам нужно использовать небольшую хитрость, чтобы начать первый параметр с пробелов. Например, если вы хотите, чтобы функция "info" выводила " hello", вы можете написать следующее:
empty :=
space := $(empty) $(empty)
$(info $(space)$(space)hello)
Kconfig использует только запятые в качестве разделителей и сохраняет все пробелы в вызове функции. Некоторые люди предпочитают добавлять пробел после каждого разделителя запятой:
$(func-name, arg1, arg2, arg3)
В этом случае func-name
получит arg1
, arg2
, arg3
. Наличие ведущих пробелов может иметь значение в зависимости от функции. То же самое относится к Make - например, $(subst .c, .o, $(sources))
- типичная ошибка; она заменяет ".c" на " .o".
В Make, пользовательская функция вызывается с помощью встроенной функции "call", вот так:
$(call my-func,arg1,arg2,arg3)
Kconfig вызывает пользовательские функции и встроенные функции одинаково. Пропуск слова "call" делает синтаксис более коротким.
В Make некоторые функции обрабатывают запятые буквально вместо разделителей аргументов. Например, $(shell echo hello, world)
выполняет команду echo hello, world
. Аналогично, $(info hello, world)
выводит "hello, world" в стандартный вывод. Можно сказать, что это полезное несоответствие.
В Kconfig для более простой реализации и грамматической согласованности запятые, которые появляются в контексте $( )
, всегда являются разделителями. Это означает, что это:
$(shell, echo hello, world)
Ошибка, потому что передаются два параметра, тогда как функция shell
принимает только один. Чтобы передать запятые в аргумент вы можете использовать такую хитрость:
comma := ,
$(shell, echo hello$(comma) world)
Предостережения
Переменная (или функция) не может быть развёрнута между несколькими токенами. Поэтому вы не можете использовать переменную в качестве сокращения для выражения, состоящего из нескольких токенов. Вот что работает:
RANGE_MIN := 1
RANGE_MAX := 3
config FOO
int "foo"
range $(RANGE_MIN) $(RANGE_MAX)
А вот что не работает:
RANGES := 1 3
config FOO
int "foo"
range $(RANGES)
Переменная не может быть развёрнута до любого ключевого слова в Kconfig. Следующее работать не будет:
MY_TYPE := tristate
config FOO
$(MY_TYPE) "foo"
default y
Очеви дно из дизайна, $(shell command)
разворачивается на этапе текстовой подстановки. Вы не можете передать символы в функцию 'shell'.
Следующее не будет работать так, как ожидается:
config ENDIAN_FLAG
string
default "-mbig-endian" if CPU_BIG_ENDIAN
default "-mlittle-endian" if CPU_LITTLE_ENDIAN
config CC_HAS_ENDIAN_FLAG
def_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG)
Вместо этого вы можете поступить следующим образом. Так любой вызов функции будет развернут статически:
config CC_HAS_ENDIAN_FLAG
bool
default $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN
default $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN