Перейти к основному содержимому

⚠️ Макроязык Kconfig

warning

Материал возможно устарел.

к сведению

Концепция

Основная идея была вдохновлена 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