Программирование ARM SCons: написание собственных сборщиков Fri, December 06 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


SCons: написание собственных сборщиков Печать
Добавил(а) microsin   

Хотя SCons из коробки предоставляет множество полезных методов для сборки обычных программных продуктов (программы, библиотеки, документы, и т. д.), вы часто можете захотеть иметь возможность сборки других типов файла, которые SCons напрямую не поддерживает. К счастью, SCons позволяет очень просто определить свои собственные объекты сборщика для любых пользовательских типов файла, которые вы хотите собрать. Фактически интерфейсы SCons для создания объектов сборщика являются достаточно гибкими и простыми для использования, так что встроенные объекты сборщика SCons созданы с помощью тех же механизмов, описанных здесь.

Написание сборщиков для запуска внешних команд. Самый простой для создания сборщик это тот, который выполняет внешнюю команду. Например, если мы хотим собрать выходной файл путем обработки входного файла командой foobuild, создание сборщика (Builder) может выглядеть так:

bld = Builder(action='foobuild < $SOURCE > $TARGET')

Все, что эта строка делает, это создание отдельного объекта сборщика. В следующей секции будет показано, как его реально использовать.

Подсоединение сборщика к construction-окружению. Объект сборщика бесполезен, пока не привязан к construction-окружению, чтобы мы могли его вызвать для организации построения файлов. Это делается через construction-переменную $BUILDERS. Переменная $BUILDERS это словарь Python, который отображает имена, с помощью которых требуется вызвать различные объекты Builder, с самими объектами. Например, если мы хотим вызвать Builder, который только что определили именем Foo, то файл SConstruct может выглядеть так:

bld = Builder(action='foobuild < $SOURCE > $TARGET')
env = Environment(BUILDERS={'Foo': bld})

Со сборщиком под именем Foo, подключенным к вашему construction-окружению, мы можем теперь вызвать его следующим образом:

env.Foo('file.foo', 'file.input')

Тогда результат запуска SCons будет такой:

% scons -Q
foobuild < file.input > file.foo

Однако имейте в виду, что переменная $BUILDERS по умолчанию в construction-окружении приходит с уже определенными объектами Builder: Program, Library, и т. д. И когда мы явно устанавливаем переменную $BUILDERS, когда создаем construction-окружение, сборщики по умолчанию больше не являются частью окружения:

bld = Builder(action='foobuild < $SOURCE > $TARGET')
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file.foo', 'file.input')
env.Program('hello.c')

Результат запуска:

% scons -Q
AttributeError: 'SConsEnvironment' object has no attribute 'Program':
  File "/home/my/project/SConstruct", line 7:
    env.Program('hello.c')

Чтобы иметь возможность использования как собственных объектов Builder, так и объектов Builder по умолчанию, вы можете либо добавить их к переменной $BUILDERS с помощью функции Append:

env = Environment()
bld = Builder(action='foobuild < $SOURCE > $TARGET')
env.Append(BUILDERS={'Foo': bld})
env.Foo('file.foo', 'file.input')
env.Program('hello.c')

.. либо можете явно установить подходяще именованный ключ в словаре $BUILDERS:

env = Environment()
bld = Builder(action='foobuild < $SOURCE > $TARGET')
env['BUILDERS']['Foo'] = bld
env.Foo('file.foo', 'file.input')
env.Program('hello.c')

В любом случае то же самое construction-окружение можно использовать как в только что определенном сборщике Foo, так и в сборщике Program по умолчанию:

% scons -Q
foobuild < file.input > file.foo
cc -o hello.o -c hello.c
cc -o hello hello.o

Обработка суффиксов файлов. Предоставлением дополнительной информации для сборщика вы можете позволить SCons добавить подходящие суффиксы файла к для target и/или файла исходного кода. Например вместо того, чтобы явно указывать, что сборщик Foo собрал target-файл file.foo из файла исходного кода file.input, вы можете с помощью ключевых слов аргумента suffix и src_suffix указать сборщику суффиксы .foo и .input, делая вызовы сборщика Foo более компактными и читаемыми:

bld = Builder(
    action='foobuild < $SOURCE > $TARGET',
    suffix='.foo',
    src_suffix='.input',
)
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file1')
env.Foo('file2')

Результат запуска:

% scons -Q
foobuild < file1.input > file1.foo
foobuild < file2.input > file2.foo

Вы также можете предоставить ключевое слово аргумента prefix, если хотите, чтобы SCons добавляла в начало имени target-файла какой-нибудь префикс.

Сборщики, которые выполняют функции Python. В SCons не нужно вызывать внешнюю команду для сборки файла. Вместо этого вы можете определить функцию Python, которую объект сборщика может вызвать для сборки вашего target-файла (или файлов). Такое определение функции сборщика выглядит следующим образом:

def build_function(target, source, env):
    # Код для сборки "target" из "source"
    ..
    return None

Аргументы этой функции сборщика следующие:

target Список Node-объектов, представляющих target (target-ы) для сборки этой функцией. Имена этих target могут быть извлечены Python-функцией str.

source Список Node-объектов, представляющих файлы исходного кода, используемые этой функцией для сборки файлов target. Имена файлов этих исходников могут быть извлечены Python-функцией str.

env Construction-окружение для сборки target-файлов. Функция может использовать любое количество construction-переменных окружения любым способом, чтобы они влияли на процесс сборки target (или нескольких target).

Эта функция будет построена как FunctionAction в системе SCons, и она должна вернуть значение 0 или None, если target (target-ы) собралась успешно. Функция может вызывать исключение (exception), или возвратить ненулевое значение, чтобы показать ошибку в процессе сборки. Для дополнительной информации по Actions см. секцию Action Objects справки man.

Когда вы определили Python-функцию, которая будет собирать ваш target-файл, определение объекта Builder для неё такое же простое, как указание имени функции, вместо того чтобы использовать внешнюю команду. Имя функции передается через параметр ключевого слова action:

def build_function(target, source, env):
    # Code to build "target" from "source"
    return None
 
bld = Builder(
    action=build_function,
    suffix='.foo',
    src_suffix='.input',
)
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file')

Обратите внимание, что вывод несколько поменялся, отражая тот факт, теперь вызывается функция Python, а не внешняя команда, чтобы выполнить сборку target-файла:

% scons -Q
foobuild < file1.input > file1.foo
foobuild < file2.input > file2.foo

Сборщики, которые создают Actions, используя Generator. Сборщики (объекты Builder) могут создавать действие (action) "на лету", используя функцию Generator (имейте в виду, что это не то же самое, что генератор Python, описанный в PEP 255 [2]). Это предоставляет большую гибкость при построении правильного списка команд для сборки вашей target. Генератор выглядит следующим образом:

def generate_actions(source, target, env, for_signature):
    return 'foobuild < %s > %s' % (target[0], source[0])

Аргументы генератора:

source Список Node-объектов, представляющих файлы исходного кода для сборки командой или другим действием, генерируемым этой функцией. Имена этих файлов могут быть извлечены Python-функцией str.

target Список Node-объектов, представляющих файлы target (одной или нескольких) для сборки командой или другим действием, генерируемым этой функцией. Имена этих файлов могут быть извлечены Python-функцией str.

env Construction-окружение для сборки target-файлов. Генератор может использовать любое количество construction-переменных окружения любым способом, чтобы они влияли на то, какую команду или действие нужно вернуть.

for_signature Флаг, который указывает, вызывается ли генератор для внесения вклада в сигнатуру сборки, в отличие от реального выполнения команды.

Генератор должен вернуть строку команды или другое действие, которое будет использоваться для сборки указанных целей (target) из указанных исходников (source).

Как только вы определили генератор, создайте сборщик для использования генератора через указание ключевого слова аргумента generator вместо action.

def generate_actions(source, target, env, for_signature):
    return 'foobuild < %s > %s' % (source[0], target[0])
 
bld = Builder(
    generator=generate_actions,
    suffix='.foo',
    src_suffix='.input',
)
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file')

Результат запуска:

% scons -Q
foobuild < file.input > file.foo

Обратите внимание, что не допускается одновременное использование для сборщика и action, и generator.

Сборщики, модифицирующие списки Target или Source: функция emitter. SCons поддерживает для сборщика возможность модификации списков файлов target, собираемых из указанных файлов source. Это делается определением функции эмиттера, принимающей как аргументы списки target-файлов и списки файлов source, переданных сборщику, и construction-окружение. Функция эмиттера должна возвратить измененные списки файлов target и source.

Для примера предположим, что вы хотите определить сборщик, который всегда вызывает программу foobuild, нужно чтобы автоматически добавлялся новый target-файл new_target и новый файл исходного кода new_source для этого вызова. Тогда файл SConstruct может выглядеть так:

def modify_targets(target, source, env):
    target.append('new_target')
    source.append('new_source')
    return target, source
 
bld = Builder(
    action='foobuild $TARGETS - $SOURCES',
    suffix='.foo',
    src_suffix='.input',
    emitter=modify_targets,
)
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file')

В результате получится такой вывод:

% scons -Q
foobuild file.foo new_target - file.input new_source

Гибкая возможность - можно использовать construction-переменную, чтобы указать разные функции эмиттера для разных construction-окружений. Чтобы это реализовать, укажите строку, содержащую разворачивание construction-переменной как эмиттера, когда вызываете функцию сборщика, и задайте для этой construction-переменной желаемую emitter-функцию в разных construction-окружениях:

bld = Builder(
    action='./my_command $SOURCES > $TARGET',
    suffix='.foo',
    src_suffix='.input',
    emitter='$MY_EMITTER',
)
 
def modify1(target, source, env):
    return target, source + ['modify1.in']
 
def modify2(target, source, env):
    return target, source + ['modify2.in']
 
env1 = Environment(BUILDERS={'Foo': bld}, MY_EMITTER=modify1)
env2 = Environment(BUILDERS={'Foo': bld}, MY_EMITTER=modify2)
env1.Foo('file1')
env2.Foo('file2')

В этом примере файлы modify1.in и modify2.in добавляются к списку source-файлов разными командами:

% scons -Q
./my_command file1.input modify1.in > file1.foo
./my_command file2.input modify2.in > file2.foo

Модификация сборщика добавлением эмиттера. Определение эмиттера для работы с пользовательским сборщиком является мощной концепцией, однако иногда все, что нужно, это использовать существующий сборщик, но всего лишь поменять концепцию того, какие цели создаются. В этом случае попытка воссоздать логику существующего сборщика, чтобы предоставить специальный эмиттер, может потребовать много работы. Типовая ситуация - когда вы хотите использовать флаг компилятора, который приводит к генерации дополнительных файлов. Например, линкер GNU принимает опцию -Map которая выводит карту линковки для файла, указанного аргументом опции. Если эта опция просто предоставлена для сборки, то SCons не будет считать map-файл линковки отслеживаемым target-файлом, что может создать различные нежелаемые эффекты.

Чтобы помочь с этим, SCons предоставляет construction-переменные, которые соответствуют нескольким стандартным сборщикам: $PROGEMITTER для сборщика Program, $LIBEMITTER для сборщика Library, $SHLIBEMITTER для сборщика SharedLibrary и $LDMODULEEMITTER для сборщика LoadableModule. Добавление эмиттера к одному из них вызовет его в дополнение к любому существующему эмиттеру для соответствующего сборщика.

В этом примере добавляется создание map в качестве флага линкера, и модифицируется стандартный эмиттер Program, чтобы обозначить сторонний эффект генерации map-файла:

env = Environment()
map_filename = "${TARGET.name}.map"
 
def map_emitter(target, source, env):
    target.append(map_filename)
    return target, source
 
env.Append(LINKFLAGS="-Wl,-Map={},--cref".format(map_filename))
env.Append(PROGEMITTER=map_emitter)
env.Program('hello.c')

Если запустить этот пример с добавлением опции, указывающей SCons сделать дамп некоторой информации об известных зависимостях, то будет показано использование опции map-файла и то, что SCons действительно знает об map-файле, и это не просто некое побочное действие компилятора:

% scons -Q --tree=prune
cc -o hello.o -c hello.c
cc -o hello -Wl,-Map=hello.map,--cref hello.o
+-.
  +-SConstruct
  +-hello
  | +-hello.o
  |   +-hello.c
  +-hello.c
  +-hello.map
  | +-[hello.o]
  +-[hello.o]

Куда следует разместить ваши пользовательские сборщики и инструменты. Директории site_scons дают вам место для размещения модулей и пакетов Python, которые вы можете импортировать в свои файлы SConscript (на верхнем уровне проекта) как дополнение, которое вы можете интегрировать в SCons (в подкаталоге site_tools), и файл site_scons/site_init.py, который считывается перед любым файлом SConstruct или SConscript file, что позволяет изменить поведение по умолчанию SCons.

Система каждого типа (Windows, Mac, Linux и т. п.) ищет каноничный набор директорий для site_scons, подробности см. в справке man. В директории site_scons верхнего уровня файла SConstruct (т. е. корневой директории проекта) всегда выполняется поиск последним, и его директория помещается первой в списке путей поиска запускаемых инструментов, что замещает последующие настроенные пути.

Если у вас есть где-то некая утилита (например SCons wiki или утилита стороннего разработчика) и вы хотите использовать это в своем проекте, то директория site_scons самое простое место для размещения такой утилиты. Утилиты (инструменты) поставляются в двух вариантах: это либо функция Python, которая работает на окружении (Environment), либо модуль Python или пакет, содержащий две функции - exists() и generate().

Одиночная функция утилиты/инструмента (tool) может быть добавлена в ваш файл site_scons/site_init.py, где она будет обнаружена и доступна для использования. Например, у вас может быть такой файл site_scons/site_init.py:

def TOOL_ADD_HEADER(env):
    """A Tool to add a header from $HEADER to the source file"""
    add_header = Builder(
        action=['echo "$HEADER" > $TARGET', 'cat $SOURCE >> $TARGET']
    )
    env.Append(BUILDERS={'AddHeader': add_header})
    env['HEADER'] = ''  # установка значения по умолчанию

.. и такой файл and a SConstruct:

# Используется TOOL_ADD_HEADER из файла site_scons/site_init.py
env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====")
env.AddHeader('tgt', 'src')

Метод TOOL_ADD_HEADER будет вызываться для добавления в окружение инструмента AddHeader.

Может быть инсталлирован более полноценный инструмент с методами exists() и generate() либо как модуль в файле site_scons/site_tools/toolname.py, либо как пакет в директории site_scons/site_tools/toolname. В случае использования пакета функции exists() и generate() находятся в файле site_scons/site_tools/toolname/__init__.py (во всех приведенных выше случаях toolname заменяется на имя инструмента). Поскольку site_scons/site_tools автоматически добавляется к началу пути поиска запускаемых утилит, то любой находящийся в нем инструмент будет доступен во всех окружениях. Кроме того, находящийся здесь инструмент переопределит инструмент с таким же именем, так что если вам надо поменять поведение встроенного инструментария, site_scons дает вам такую возможность.

У многих разработчиков есть коллекция служебных функций-утилит Python, которые они хотели бы подключить в свои файлы SConscript files. Для этого просто поместите их в site_scons/my_utils.py или любой модуль с допустимым для Python именем. Например, вы можете сделать что-то подобное в site_scons/my_utils.py, чтобы добавить функции build_id и MakeWorkDir:

from SCons.Script import *  # для Execute и Mkdir
 
def build_id():
    """Return a build ID (stub version)"""
    return "100"
 
def MakeWorkDir(workdir):
    """Create the specified dir immediately"""
    Execute(Mkdir(workdir))

И тогда в файле SConscript, или любом другом дочернем SConscript, использующемся в вашей сборке, вы можете импортировать и использовать my_utils:

import my_utils
 
print("build_id=" + my_utils.build_id())
my_utils.MakeWorkDir('/tmp/work')

Вы можете поместить эту коллекцию в его собственной модуль в site_scons и импортировать его как в этом примере, или можете подключить его в site_scons/site_init.py, который импортируется автоматически (если вы не запретили директории site). Обратите внимание, что для ссылки на объекты в пространстве имен SCons, таких как Environment, Mkdir или Execute в любом файле, отличном от SConstruct или SConscript, всегда нужно делать следующее:

from SCons.Script import *

Это также верно для модулей в site_scons, также и для site_scons/site_init.py.

Вы можете использовать любые пользовательские или системные директории, такие как ~/.scons/site_scons, вместо ./site_scons, или использовать опцию --site-dir, чтобы указать свою собственную директорию. Файл site_init.py и директория site_tools будут размещены в указанной директории. Чтобы полностью отключить использование директории site_scons, даже если она существует, используйте опцию --no-site-dir.

[Можно не писать сборщик: Command Builder]

Создание сборщика и подсоединение его к construction-окружению обеспечивает большую гибкость, когда вы хотите повторно использовать действия для сборки нескольких файлов одинакового типа. Однако это может быть громоздко, если для сборки одного файла (или группы файлов) нужно выполнить одну специальную команду. Для таких ситуаций SCons поддерживает сборщик Command, который организует выполнение определенного действия для сборки определенного файла или файлов. Это работает похоже на другие сборщики (такие как Program, Object, и т. д.), но Command принимает дополнительный аргумент команды для выполнения сборки файла:

env = Environment()
env.Command('foo.out', 'foo.in', "sed 's/x/y/' < $SOURCE > $TARGET")

При выполнении SCons запустит указанную команду, ожидаемо заменяя $SOURCE и $TARGET:

% scons -Q
sed 's/x/y/' < foo.in > foo.out

Это часто удобнее, чем создание объекта Builder, и добавление его в переменную $BUILDERS в construction-окружении.

Обратите внимание, что для сборщика Command Builder можно указать любое легальное SCons Action, такое как функция Python:

env = Environment()
 
def build(target, source, env):
    # Все, что нужно, чтобы выполнить сборку
    return None
 
env.Command('foo.out', 'foo.in', build)

Результат запуска:

% scons -Q
build(["foo.out"], ["foo.in"])

Здесь $SOURCE и $TARGET также расширяются в исходный код и цель сборки, так что вы можете написать:

env.Command('${SOURCE.basename}.out', 'foo.in', build)

.. что выполнит то же самое, что и предыдущий пример, но позволит избежать повторений.

Может быть полезным использовать ключевое слово action для указания действия, это сделает скрипт более понятным:

env.Command('${SOURCE.basename}.out', 'foo.in', action=build)

Метод, описанный выше в секции "Управление выводом команд сборки: переменные $*COMSTR для управления выводом хорошо работает, когда используется с предварительно определенными сборщиками, которые имеют для этой цели предварительно определенные переменные *COMSTR. Однако для вызова Command это не тот случай, здесь SCons не обладает специфическими знаниями no предстоящему действию. Если аргумент action для Command не объект Action, то он будет автоматически создан с подходящими значениями по умолчанию, которые включают выводимое сообщение, основанное на типе действия (action type). Однако вы также можете сконструировать объект Action самостоятельно, чтобы передать его в Command, что даст больше контроля над выводом. Ниже показан усовершенствованный предыдущий пример, показывающий реализацию этого способа:

env = Environment()
 
def build(target, source, env):
    # Все, что нужно, чтобы выполнить сборку
    return None
 
act = Action(build, cmdstr="Building ${TARGET}")
env.Command('foo.out', 'foo.in', action=act)

Результат запуска:

% scons -Q
Building foo.out

[Расширение SCons: Pseudo-Builders и функция AddMethod]

Функция AddMethod используется для добавление метода в окружение. Она обычно используется для добавления псевдо-сборщика (pseudo-builder) - функции, которая выглядит как Builder но вместо этого оборачивает внутри себя несколько других сборщиков, или другими словами обрабатывает свои аргументы перед вызовом одного или большего количества сборщиков. В следующем примере мы хотим инсталлировать программу в стандартную иерархию директорий /usr/bin, то также копируем её в локальную директорию install/bin, из которой может быть собран пакет:

def install_in_bin_dirs(env, source):
    """Install source in both bin dirs"""
    i1 = env.Install("$BIN", source)
    i2 = env.Install("$LOCALBIN", source)
    return [i1[0], i2[0]] # вернет список, как обычный сборщик
 
env = Environment(BIN='/usr/bin', LOCALBIN='#install/bin')
env.AddMethod(install_in_bin_dirs, "InstallInBinDirs")
env.InstallInBinDirs(Program('hello.c')) # установит hello в обе директории bin

Результат запуска:

% scons -Q /
cc -o hello.o -c hello.c
cc -o hello hello.o
Install file: "hello" as "/usr/bin/hello"
Install file: "hello" as "install/bin/hello"

Как уже упоминалось, pseudo-builder также предоставляет дополнительную гибкость для парсинга аргументов - большую, чем можно было получить с обычным сборщиком. Следующий пример показывает pseudo-builder с именованным аргументом, который модифицирует имя файла, и отдельный аргумент для файла ресурса (вместо того, чтобы дать сборщику разобраться с файлом по его расширению). Этот пример также демонстрирует использование глобальной функции AddMethod для добавления метода к глобальному классу Environment, поэтому он будет использоваться во всех впоследствии созданных окружениях.

def BuildTestProg(env, testfile, resourcefile, testdir="tests"):
    """Build the test program;
    prepends "test_" to src and target,
    and puts target into testdir."""
    srcfile = "test_%s.c" % testfile
    target = "%s/test_%s" % (testdir, testfile)
    if env['PLATFORM'] == 'win32':
        resfile = env.RES(resourcefile)
        p = env.Program(target, [srcfile, resfile])
    else:
        p = env.Program(target, srcfile)
    return p
 
AddMethod(Environment, BuildTestProg)
 
env = Environment()
env.BuildTestProg('stuff', resourcefile='res.rc')

На Linux это генерирует следующий результат:

% scons -Q
cc -o test_stuff.o -c test_stuff.c
cc -o tests/test_stuff test_stuff.o

На Windows получится следующее:

C:\>scons -Q
rc /nologo /fores.res res.rc
cl /Fotest_stuff.obj /c test_stuff.c /nologo
link /nologo /OUT:tests\test_stuff.exe test_stuff.obj res.res
embedManifestExeCheck(target, source, env)

Использование AddMethod лучше, чем просто добавить экземпляр метод в construction-окружение, потому что он вызывается как правильный метод, и потому что AddMethod предоставляет возможность копирования метода в любой клон экземпляра construction-окружения.

[Расширение SCons: как написать собственный сканер]

В SCons встроены сканеры, которые знают, как обработать исходный код на языках C/C++, Fortran, D, IDL, LaTeX, Python и SWIG для получения информации о других файлах, от которых зависят цели сборки - например в случае файлов, которые обрабатывает препроцессор C это заголовочные файлы *.h, указанные директивами #include в файлах исходного кода. Вы можете использовать те же механизмы, которые SCons использует для создания своих собственных сканеров, чтобы написать сканер для своих типов файлов, о которых система SCons не знает, и которые она не может сканировать "из коробки".

Пример простого сканера. Предположим, что мы хотим создать сканер для файлов *.foo. Файл *.foo содержит некий текст, который будет обработан, и который может подключать другие файлы в строках, где за директивой подключения include идет имя файла зависимости:

include filename.foo

Сканирование файла должно быть обработано функцией Python, которую вам нужно предоставить. Ниже показана функция, которая в нашем примере будет использовать Python-модуль re для сканирования строк:

import re
 
include_re = re.compile(r'^include\s+(\S+)$', re.M)
 
def kfile_scan(node, env, path, arg):
    contents = node.get_text_contents()
    return env.File(include_re.findall(contents))

Важно отметить, что вам необходимо из функции сканера возвратить список узлов File, простые строки для имен файлов не подойдут. Как и в примерах, показанных здесь, вы можете использовать функцию File в своем текущем construction-окружении, чтобы создать узлы (nodes) на лету из последовательности имен файлов с относительными путями.

Функция сканера должна принять 4 указанных аргумента, и возвратить список неявных зависимостей. Предположительно это будут зависимости, найденные анализом содержимого файла, хотя функция может выполнить любые манипуляции для создания списка зависимостей. Аргументы функции сканера:

node Объект узла SCons (node-объект), представляющий сканируемый файл. Имя пути файла может использоваться для преобразования в строку функцией str(), или внутренним SCons-методом объекта get_text_contents(), который может использоваться для извлечения содержимого.

env Construction-окружение, действующее для этого сканирования. Функция сканера может выбрать использование construction-переменных из этого окружения, которые могут повлиять на её поведение.

path Список директорий, формирующие пути поиска подключаемых файлов для этого сканера. Это как SCons обрабатывает переменные $CPPPATH и $LIBPATH.

arg Опциональный аргумент, который вы можете выбрать для передачи этой функции сканера в различных экземплярах сканера.

Объект сканера создается функцией Scanner, которая обычно принимает аргумент skeys для привязки суффикса файла к этому сканеру. Объект Scanner должен затем быть связан с construction-переменной $SCANNERS в текущем construction-окружении, обычно с помощью метода Append:

kscan = Scanner(function=kfile_scan, skeys=['.k'])
env.Append(SCANNERS=kscan)

Когда мы соединим все вместе, получится следующее:

import re
include_re = re.compile(r'^include\s+(\S+)$', re.M)
 
def kfile_scan(node, env, path):
    contents = node.get_text_contents()
    includes = include_re.findall(contents)
    return env.File(includes)
 
kscan = Scanner(function=kfile_scan, skeys=['.k'])
 
env = Environment(ENV={'PATH': '/usr/local/bin'})
env.Append(SCANNERS=kscan)
 
env.Command('foo', 'foo.k', 'kprocess < $SOURCES > $TARGET')

Добавление пути поиска для сканера: FindPathDirs. Если рассматриваемый инструментарий сборки будет использовать переменную пути для поиска подключаемых файлов или других зависимостей, то для Scanner нужно взять переменную пути для той же цели - например, обычные сборщики SCons для этого используют $CPPPATH и $LIBPATH. Путь поиска передается в ваш сканер как аргумент path (переменная пути поиска). Переменные пути могут быть списком узлов, строками с разделителем в виде точки с запятой, или даже содержать construction-переменные, которые должны быть развернуты. SCons предоставляет функцию FindPathDirs, которая вернет вызываемое преобразование для указанного пути (заданное как имя construction-переменной SCons) в список путей на момент вызова сканера. Отложенное вычисление до этого момента позволяет, например, чтобы путь содержал ссылки $TARGET, которые отличаются для каждого сканируемого файла.

Использовать FindPathDirs очень просто. Продолжая пример выше, используем KPATH как construction-переменную, которая содержит путь поиска (аналогично $CPPPATH), мы просто изменим вызов функции Scanner для подключения функции через ключевое слово аргумента path_function:

kscan = Scanner(function=kfile_scan, skeys=['.k'], path_function=FindPathDirs('KPATH'))

FindPathDirs вернет вызываемый объект, который, будучи вызванным, развернет элементы в env['KPATH'] и укажет сканеру искать файлы зависимости в этих директориях. В список поиска также будут правильно добавлены связанный репозиторий и директории варианта. Возвращенный метод сохраняет путь эффективным способом, так что просмотр списка путей сработает быстро, даже когда могут понадобиться переменные замены. Это важный момент, поскольку в типовой сборке могут сканироваться многие файлы.

Использование сканеров вместе со сборщиками. Один из подходов к внедрению сканеров заключается в использовании вместе с Builder. При создании сборщика мы можем использовать два соответствующих необязательных параметра: source_scanner (сканер исходного кода) и target_scanner (сканер файлов целей, когда они сгенерированы).

import re
 
include_re = re.compile(r'^include\s+(\S+)$', re.M)
 
def kfile_scan(node, env, path, arg):
    contents = node.get_text_contents()
    return env.File(include_re.findall(contents))
 
kscan = Scanner(function=kfile_scan, skeys=['.k'], path_function=FindPathDirs('KPATH')
 
def build_function(target, source, env):
    # Код для сборки "target" из "source"
    return None
 
bld = Builder(
    action=build_function,
    suffix='.foo',
    source_scanner=kscan,
    src_suffix='.input',
)
env = Environment(BUILDERS={'Foo': bld})
env.Foo('file')

Функция эмиттера может модифицировать список source-файлов или target-файлов, переданных в action-функцию, когда будет запущен сборщик.

Функция сканера не будет влиять на список source-файлов или target-файлов, которые сборщик видит во время действия сборки (build action). Однако функция сканера будет влиять на то, что сборщик должен пересобрать (например, если был изменен любой из исходных файлов для сканера).

[Ссылки]

1. SCons: руководство пользователя, быстрый старт.
2. gnu/gettext http://ftp.gnu.org/gnu/gettext/.
3. SCons: мультиплатформенная конфигурация.

 

Добавить комментарий


Защитный код
Обновить

Top of Page