SCons: рабочие окружения Печать
Добавил(а) microsin   

Environment (рабочее окружение, среда выполнения кода) это набор значений, которые могут повлиять на то, как выполняется программа. SCons различает три разных типа рабочих окружений, которые могут повлиять на поведение самой SCons (при условии конфигурации в файлах SConscript), а также компиляторов и других инструментов, которые запускает SCons:

Внешнее окружение. External Environment это набор переменных в пользовательском окружении в момент, когда пользователь запускает SCons. Эти переменные не являются автоматически частью сборки SCons, однако они доступны для проверки при необходимости. См. далее секцию "Использование переменных из External Environment".

Окружение конструирования. Construction Environment это отдельный объект, созданный в файле SConscript и содержащий значения, которые влияют на то, как SCons решает, какое действие использовать для сборки target, и даже для определения того, какие target-ы должны быть собраны из файлов исходного кода. Одной из наиболее мощных функций SCons является возможность создание нескольких construction-окружений, включая возможность клонирования нового, настроенного construction-окружения из уже существующего. См. далее "Construction-окружения".

Окружение выполнения. Execution Environment это значения, которые SCons устанавливает при выполнении внешней команды (такой как компилятор или линкер), чтобы выполнить сборку одной или большего количества target. Обратите внимание, что это не то же самое, что external environment. См. далее "Управление Execution Environment для выданных команд".

В отличие от Make, SCons не делает автоматически копию или импорт значений между различными окружениями (за исключением явного клонирования construction-окружения, когда наследуются значения клонируемого родителя). Это преднамеренный выбор реализации SCons для того, чтобы обеспечить по умолчанию воспроизводимость сборок независимо от значений external environment пользователя. Это позволяет избежать целого класса проблем со сборками, в которых локальная сборка разработчика работает, поскольку настройка пользовательской переменной приводит к использованию другого компилятора или опции сборки, но проверенное изменение прерывает официальную сборку, потому что использует другие установки переменных окружения.

Обратите внимание, что создатель SConscript может просто организовать копирование или импорт переменных между окружениями, и это часто может быть очень полезно (или даже совершенно необходимо), чтобы упростить для разработчиков настройку сборки подходящими способами. Дело не в том, что копирование переменных между разными окружениями это зло, чего следует всегда избегать. Вместо этого разработчик системы сборки должен принимать осознанные решения, как и когда импортировать переменную из одной среды в другую, достигая баланс между тем, чтобы сделать сборку повторяемой и тем, чтобы она была удобной для использования в другом месте.

Если вы не очень хорошо знакомы с языком программирования Python, то нужно рассмотреть тип данных словаря языка Python (dictionary). Тип dictionary (который также известен по терминами mapping, ассоциативный массив и хранилище ключ-значение) связывает друг с другом пары ключ и значение, так что запрос к словарю по ключу возвращает соответствующее значение, и назначение значения ключу создает ассоциацию (либо новую настройку), если такой ключ неизвестен, либо замену предыдущей ассоциации, если ключ в словаре уже существует. Значения из словаря могут быть получены с помощью обращения к элементу словаря (с указанием имени ключа в квадратных скобках []), и словари также предоставляют метод get, который отвечает значением по умолчанию, либо None, либо значением, которое вы предоставили в качестве второго аргумента, если такого ключа нет в словаре, что позволит избежать ошибки. Синтаксис инициализации словаря использует фигурные скобки{}. Ниже дано несколько примеров, которые можно повторить в интерактивной командной строке интерпретатора Python (>>> это приглашение ввода интерпретатора):

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> print(tel)
{'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> 'guido' in tel
True
>>> print(tel['jack'])
Traceback (most recent call last):
  File "< stdin>", line 1, in < module>
KeyError: 'jack'
>>> print(tel.get('jack'))
None

Construction-окружение написано так, чтобы работать наподобие словаря Python, и construction-переменная $ENV в construction-окружении является словарем Python. Значение os.environ, которое Python использует для доступа к external environment, также является словарем. Понимание концепции словаря Python важно для материала руководства пользователя SCons.

Использование переменных из External Environment. Параметры external environment (переменные окружения), которые используются при выполнении SCons, доступны в Python-словаре os.environ. Этот синтаксис означает, что environ является атрибутом модуля os. В Python для доступа к содержимому модуля нужно сначала его импортировать, для чего нужно использовать оператор import os в любом файле SConscript, в котором вы хотите использовать значения из пользовательского external environment.

import os
 
print("Shell is", os.environ['SHELL'])

Еще более удобно использовать словарь os.environ в ваших файлах SConscript для инициализации construction-окружения значениями из пользовательского external environment. В следующем разделе будет рассмотрено, как это делается.

[Construction-окружения]

Редко бывает, что большой проект ПО должен собираться всегда одинаковым образом. Например, разные исходные файлы могут потребовать разных опций командной строки, или разные исполняемые программы должны быть линкованы с разными библиотеками. SCons удовлетворяет этим различным требованиям сборки, позволяя вам создавать и конфигурировать несколько construction-окружений, которые управляют тем, как выполняется сборка ПО. Construction-окружение это объект, в котором есть несколько связанных construction-переменных, у каждой из которых есть имя и значение, что работает так же, как dictionary языка Python. В construction-окружении также есть набор подключенных методов Builder, которые будут рассмотрены далее.

Создание construction-окружения: функция Environment. Construction-окружение создается методом Environment:

env = Environment()

По умолчанию SCons инициализирует каждый новое construction-окружение набором construction-переменных на основании инструментов, которые найдены на вашей системе, а также набора методов сборщика по умолчанию, необходимых для использования этих инструментов. Construction-переменные инициализируются значениями, описывающим компилятор C, компилятором Fortran, линкером, и т. д., а также командными строками для их вызова.

При инициализации construction-окружения можно задать значения construction-переменных среды для управления построением программы. Например:

env = Environment(CC='gcc', CCFLAGS='-O2')
env.Program('foo.c')

Construction-окружение в этом примере по-прежнему инициализируется теми же значениями construction-переменной по умолчанию, за исключением того, что пользователь явно указал использование C-компилятора GNU gcc, и что при компиляции объектного файла должен использоваться флаг опции оптимизации -O2 (оптимизация второго уровня). Другими словами, явные инициализации $CC и $CCFLAGS переназначают значения по умолчанию этого нового созданного construction-окружения. Таким образом, запуск этого примера будет выглядеть следующим образом:

% scons -Q
gcc -o foo.o -c -O2 foo.c
gcc -o foo foo.o

Извлечение значений из Construction Environment. Вы можете брать отдельные значения, известные как construction-переменные, используя тот же синтаксис, который используется для доступа к отдельным именованным элементам словаря Python:

env = Environment()
print("CC is: %s" % env['CC'])
print("LATEX is: %s" % env.get('LATEX', None))

Этот пример файла SConstruct не содержит инструкций для сборки любых target, однако потому что это все еще допустимый SConstruct, он будет обработан и вызовы операторов print языка Python выведут для нас значения $CC и $LATEX (помните про использование метода .get() для выборки, когда мы получаем обратно значение по умолчанию вместо ошибки, если переменная не установлена):

% scons -Q
CC is: cc
LATEX is: None
scons: `.' is up to date.

Construction-окружение фактически это объект со связанными методами и атрибутами. Если вы хотите получить прямой доступ только к словарю construction-переменных, то можете извлекать их, используя метод env.Dictionary (хотя использование этого метода редко бывает необходимо):

env = Environment(FOO='foo', BAR='bar')
cvars = env.Dictionary()
for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
   print("key = %s, value = %s" % (key, cvars[key]))

Этот файл SConstruct напечатает для нас указанные элементы словаря на POSIX-системах следующим образом:

% scons -Q
key = OBJSUFFIX, value = .o
key = LIBSUFFIX, value = .a
key = PROGSUFFIX, value = 
scons: `.' is up to date.

И на Windows:

C:\>scons -Q
key = OBJSUFFIX, value = .obj
key = LIBSUFFIX, value = .lib
key = PROGSUFFIX, value = .exe
scons: `.' is up to date.

Если вы хотите организовать цикл и напечатать значения всех construction-переменных в construction-окружении, то код Python, который выводит переменные в сортированном порядке может выглядеть так:

env = Environment()
for item in sorted(env.Dictionary().items()):
   print("construction variable = '%s', value = '%s'" % item)

Следует отметить, что для предыдущего примера в действительности есть метод construction-окружения, который делает то же самое проще, и пытается точно так же отформатировать вывод:

env = Environment()
print(env.Dump())

Развертывание значений из Construction Environment: метод subst. Другой способ получить информацию из construction-окружения это использование метода subst в строке, содержащей $ расширения имен construction-переменных. В предыдущей секции был дан простой пример, где использовалось env['CC'] для выборки значения $CC, можно также переписать так:

env = Environment()
print("CC is: %s" % env.subst('$CC'))

Одно из достоинств использования subst для разворачивания строк состоит в том, construction-переменные в результате get повторно разворачиваются до тех пор, пока в строке не останется re-expanded расширений. Так что простая выборка значения наподобие $CCCOM:

env = Environment(CCFLAGS='-DFOO')
print("CCCOM is: %s" % env['CCCOM'])

.. напечатает неразвернутое значение $CCCOM, показывая нам construction-переменные, которые все еще нужно развернуть:

% scons -Q
CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES
scons: `.' is up to date.

Вызов метода subst на $CCOM, однако:

env = Environment(CCFLAGS='-DFOO')
print("CCCOM is: %s" % env.subst('$CCCOM'))

.. рекурсивно развернет все construction-переменные, снабженные префиксом $ (знак доллара), показывая конечный вывод:

% scons -Q
CCCOM is: gcc -DFOO -c -o
scons: `.' is up to date.

Обратите внимания, что мы не развернули это в контексте сборки чего-либо, нет развертки target-файлов или исходных файлов для $TARGET и $SOURCES.

Решение проблем с разверткой переменной. Если произошла проблема с разворачиванием construction-переменной, по умолчанию она развертывается как '' (пустая строка), и это не приводит к сбою scons.

env = Environment()
print("value is: %s"%env.subst( '- >$MISSING< -' ))

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

% scons -Q
value is: - >< -
scons: `.' is up to date.

Это поведение по умолчанию можно поменять, используя функцию AllowSubstExceptions. Когда возникнет проблема с разверткой переменной, будет сгенерировано исключение, и функция AllowSubstExceptions управляет тем, какие из исключений будут фатальными, и какие могут не вызывать сбоя. По умолчанию NameError и IndexError это два исключения, которые могут произойти: таким образом, чтобы не вызывать сбоя scons, эти исключения перехватываются, переменная разворачивается в '' и scons продолжает выполнение. Для требования, чтобы существовали все имена construction-переменных, и не допускались индексы, выходящие за пределы, вызовите AllowSubstExceptions без дополнительных аргументов.

AllowSubstExceptions()
env = Environment()
print("value is: %s"%env.subst( '- >$MISSING< -' ))

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

% scons -Q
 
scons: *** NameError `name 'MISSING' is not defined' trying to evaluate `$MISSING'
File "/home/my/project/SConstruct", line 3, in < module>

Это можно использовать также для того, чтобы могли срабатывать другие исключения, что более полезно с синтаксисом ${...} construction-переменной. Например, это позволит сработать исключению деления на ноль в расширении переменной, в дополнение к разрешенным исключениям по умолчанию:

AllowSubstExceptions(IndexError, NameError, ZeroDivisionError)
env = Environment()
print("value is: %s"%env.subst( '- >${1 / 0}< -' ))

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

% scons -Q
value is: - >< -
scons: `.' is up to date.

Если AllowSubstExceptions была вызвана несколько раз, то каждый вызов полность меняет предыдущий список разрешенных для генерации исключений.

Управление construction-окружением по умолчанию: функция DefaultEnvironment. Все функции сборщика, которые были показаны до сих пор, наподобие Program и Library, используют construction-окружение, которое содержит настройки для различных компиляторов и других инструментов, которые SCons конфигурирует по умолчанию, или иначе говоря знает о них, и которые были обнаружены в вашей системе. Если они не вызываются как методы в определенном construction-окружении, то они используют construction-окружение по умолчанию, чтобы многие конфигурации "просто работали" для сборки ПО, используя готовые доступные инструменты, с минимальными изменениями в конфигурации.

Если необходимо, то вы можете управлять construction-окружением по умолчанию, используя функцию DefaultEnvironment для инициализации различных настроек, передавая их как ключевые аргументы:

DefaultEnvironment(CC='/usr/local/bin/gcc')

В этом примере все вызовы Program или Object будут собирать объектные файлы с вызовом компилятора /usr/local/bin/gcc.

Функция DefaultEnvironment возвратит инициализированный объект construction-окружения по умолчанию, которым можно затем манипулировать как и любым другим construction-окружением (обратите внимание, что окружение по умолчанию работает как синглтон (singleton, т. е. одиночный объект, у которого может быть только один экземпляр), поэтому ключевые аргументы обрабатываются только при первом вызове, и любые последующие вызовы возвратят существующий объект. Таким образом следующее будет эквивалентно предыдущему примеру, устанавливая переменную $CC в значение /usr/local/bin/gcc, но как отдельный шаг после того, как было инициализировано construction-окружение по умолчанию:

def_env = DefaultEnvironment()
def_env['CC'] = '/usr/local/bin/gcc'

Одним из чаще всего используемых способов вызова функции DefaultEnvironment является ускорение инициализации SCons. В попытках настроить конфигурации по умолчанию так, чтобы они "просто работали", SCons фактически будет искать в локальной системе установленные компиляторы и другие утилиты. Этот поиск занимает некоторое время, особенно на системах с медленной сетевой файловой системой. Если вы знаете, какой компилятор (или компиляторы) и/или другие утилиты нужно сконфигурировать, то можете управлять поиском SCons путем указания определенных модулей инструментов, с помощью которых можно инициализировать construction-окружение по умолчанию:

def_env = DefaultEnvironment(tools=['gcc', 'gnulink'], CC='/usr/local/bin/gcc')

В этом примере явно указано SCons конфигурировать окружение по умолчанию для обычных настроек компилятора и линкера GNU (без необходимости для этой цели искать их или любые другие утилиты), и в частности указано использовать компилятор в каталоге /usr/local/bin/gcc.

Несколько construction-окружений. Реальное достоинство construction-окружений заключается в том, что вы можете создать их несколько по мере необходимости, чтобы каждое окружение было адаптировано к различным способам создания какого-либо ПО или другого файла. Если, например, нужно собрать одну программу с флагом оптимизации -O2, и другую с флагом -g (режим отладки, оптимизация отключена), то это можно сделать следующим образом:

opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
 
opt.Program('foo', 'foo.c')
dbg.Program('bar', 'bar.c')

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

% scons -Q
cc -o bar.o -c -g bar.c
cc -o bar bar.o
cc -o foo.o -c -O2 foo.c
cc -o foo foo.o

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

opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
 
opt.Program('foo', 'foo.c')
dbg.Program('foo', 'foo.c')

.. то произойдет ошибка:

% scons -Q
 
scons: *** Two environments with different actions were specified for the same target: foo.o
File "/home/my/project/SConstruct", line 6, in < module>

Причина ошибки в том, что два вызова Program неявно говорят SCons генерировать объектный файл foo.o, у одного из которых значение $CCFLAGS содержит флаг -O2, и у другого $CCFLAGS со значением -g. SCons просто не может решить, у какого из них преимущество перед другим, и генерирует эту ошибку. Чтобы решить такую проблему, необходимо явно указать для каждой компиляции с отдельным окружением также и отдельные имена целевых файлов для вызова Object сборщика, когда компилируется модуль foo.c:

opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
 
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)

Обратите внимание, что каждый вызов Object возвратит значение в виде внутреннего объекта SCons, который представляет собираемый объектный файл. Затем мы используем этот объект в качестве входного для метода Program сборщика. Это позволяет избежать необходимости указания имени объектного файла в нескольких местах, и дает компактный, читаемый файл SConstruct. Наш вывод SCons тогда будет следующим:

% scons -Q
cc -o foo-dbg.o -c -g foo.c
cc -o foo-dbg foo-dbg.o
cc -o foo-opt.o -c -O2 foo.c
cc -o foo-opt foo-opt.o

Создание копий construction-окружений: метод Clone. Иногда вы можете захотеть, чтобы больше одного construction-окружения использовали одинаковые значения для одной или большего количества переменных. Вместо того, чтобы повторно указывать все общие переменные при создании каждого construction-окружения, вы можете использовать метод env.Clone для создания копии construction-окружения.

Как и вызов Environment, который создает construction-окружение, метод Clone принимает назначения construction-переменных, которые будут менять значения в копии construction-окружения. Для примера предположим, что мы хотим использовать gcc для создания трех версий программы, одной оптимизированной, одной для отладки, и одной без этих настроек. Мы могли бы сделать это, создав "базовое" construction-окружение, которое установит $CC в gcc, и затем создать две его копии, в одной из которых $CCFLAGS установлена для оптимизации, и в другой $CCFLAGS установлена для отладки:

env = Environment(CC='gcc')
opt = env.Clone(CCFLAGS='-O2')
dbg = env.Clone(CCFLAGS='-g')
 
env.Program('foo', 'foo.c')
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)

Тогда наш результат компиляции будет выглядеть вот так:

% scons -Q
gcc -o foo.o -c foo.c
gcc -o foo foo.o
gcc -o foo-dbg.o -c -g foo.c
gcc -o foo-dbg foo-dbg.o
gcc -o foo-opt.o -c -O2 foo.c
gcc -o foo-opt foo-opt.o

Замена значений: метод Replace. Мы можем заменить значения существующих construction-переменных, используя метод env.Replace:

env = Environment(CCFLAGS='-DDEFINE1')
env.Replace(CCFLAGS='-DDEFINE2')
env.Program('foo.c')

Заменяемое значение (-DDEFINE2 в примере выше) полностью заменит значение в construction-окружении:

% scons -Q
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o

Вы можете безопасно вызвать Replace для construction-переменных, которые не существуют в construction-окружении:

env = Environment()
env.Replace(NEW_VARIABLE='xyzzy')
print("NEW_VARIABLE = %s" % env['NEW_VARIABLE'])

В этом случае construction-переменная просто будет добавлена в construction-окружение:

% scons -Q
NEW_VARIABLE = xyzzy
scons: `.' is up to date.

Поскольку переменные не расширяются до тех пор, пока construction-окружение не будет фактически использоваться для сборки target-ов, а также поскольку вызовы функций и методов SCons не зависят от порядка их вызова, то последняя замена "выиграет", и будет использоваться для сборки всех target, независимо от порядка, в котором вызовы Replace() перемежаются с вызовами методов сборщика:

env = Environment(CCFLAGS='-DDEFINE1')
print("CCFLAGS = %s" % env['CCFLAGS'])
env.Program('foo.c')
 
env.Replace(CCFLAGS='-DDEFINE2')
print("CCFLAGS = %s" % env['CCFLAGS'])
env.Program('bar.c')

Время, когда происходит реальная замена относительно момента сборки target-ов, становится заметным, если запустить scons без опции -Q:

% scons
scons: Reading SConscript files ...
CCFLAGS = -DDEFINE1
CCFLAGS = -DDEFINE2
scons: done reading SConscript files.
scons: Building targets ...
cc -o bar.o -c -DDEFINE2 bar.c
cc -o bar bar.o
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o
scons: done building targets.

Поскольку замена происходит по мере чтения файлов SConscript, переменная $CCFLAGS уже установлена в -DDEFINE2 в момент сборки цели foo.o, несмотря на то, что метод Replace в файле SConscript встречается позже.

Установка значений только если они еще не определены: метод SetDefault. Иногда полезно иметь возможность указать, что construction-переменная должна быть установлена только в том случае, если в construction-оружении эта переменная еще не определена. Это можно сделать с помощью метода env.SetDefault, который ведет себя подобно методу setdefault объектов словаря Python:

env.SetDefault(SPECIAL_FLAG='-extra-option')

Это особенно полезно при написании собственных модулей Tool для применения переменных к construction-окружениям.

Добавление к концу значения: метод Append. Вы можете добавить значение к существующей construction-переменной, используя метод env.Append:

env = Environment(CPPDEFINES=['MY_VALUE'])
env.Append(CPPDEFINES=['LAST'])
env.Program('foo.c')

Обратите внимание, что $CPPDEFINES предпочтительный способ установить определения препроцессора, поскольку SCons будет генерировать аргументы командной строки, используя корректный для платформы префикс/суффикс, что делает возможным портирования процесса компиляции. Если вы используете $CCFLAGS и $SHCCFLAGS, то нужно подключить их в конечную форму, что делает проект менее портируемым.

% scons -Q
cc -o foo.o -c -DMY_VALUE -DLAST foo.c
cc -o foo foo.o

Если construction-переменная еще не существует, то метод Append её создаст:

env = Environment()
env.Append(NEW_VARIABLE = 'added')print("NEW_VARIABLE = %s"%env['NEW_VARIABLE'])

Результат компиляции:

% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.

Обратите внимание, что функция Append пытается быть "умной", в контексте того, как новое значение добавляется к старому. Если оба эти значения строки, то предыдущая строка и новая строка просто склеиваются друг с другом. Подобным образом, если обе переменные списки, то списки объединяются друг с другом. Если, однако, одно значение это строка, а другое список, то строка добавляется в список как новый элемент.

Добавление уникальных значений: метод AppendUnique. Иногда полезно добавить новое значение в конец переменной только в том случае, если существующая construction-переменная еще не содержит это значение. Это можно сделать методом env.AppendUnique:

env.AppendUnique(CCFLAGS=['-g'])

В примере выше -g добавляется только если переменная $CCFLAGS еще не содержит значение -g.

Подстановка вперед: метод Prepend. Вы можете добавить значение к началу существующей construction-переменной, используя метод env.Prepend:

env = Environment(CPPDEFINES=['MY_VALUE'])
env.Prepend(CPPDEFINES=['FIRST'])
env.Program('foo.c')

Тогда SCons генерирует аргументы define препроцессора из значений CPPDEFINES с корректным префиксом/суффиксом. Например на Linux или POSIX сгенерируются следующие аргументы: -DFIRST и -DMY_VALUE

% scons -Q
cc -o foo.o -c -DFIRST -DMY_VALUE foo.c
cc -o foo foo.o

Если construction-переменная еще не существует, то метод Prepend её создаст:

env = Environment()
env.Prepend(NEW_VARIABLE='added')print("NEW_VARIABLE = %s" % env['NEW_VARIABLE'])

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

% scons -Q
NEW_VARIABLE = added
scons: `.' is up to date.

Наподобие функции Append, функция Prepend также "по-умному" пытается подставлять новое значение к старому. Если оба значения это строки, то они просто склеиваются. Подобным образом, если обе переменные списки, то списки объединяются друг с другом. Если, однако, одна переменная это строка, а другая список, то строка добавляется в список как новый элемент списка.

Подстановка вперед уникальных значений: метод PrependUnique. Иногда полезно добавить новое значение в начало переменной только в том случае, если существующая construction-переменная еще не содержит это значение. Это можно сделать методом env.PrependUnique:

env.PrependUnique(CCFLAGS=['-g'])

В примере выше -g добавится только если переменная $CCFLAGS не содержит значения -g.

Переустановка значения construction-переменной. Вместо того, чтобы клонировать construction-окружение для определенных задач, вы можете переназначить или добавить construction-переменные, когда вызываете метод сборщика, путем передачи ему ключевых аргументов. Значения этих переназначенных или добавленных переменных будут влиять на сборку только этого target, и не повлияют на другие части сборки. Например, если вы хотите добавить дополнительные библиотеки только для одной программы:

env.Program('hello', 'hello.c', LIBS=['gl', 'glut'])

.. или сгенерировать общую библиотеку с нестандартным суффиксом:

env.SharedLibrary(
    target='word',
    source='word.cpp',
    SHLIBSUFFIX='.ocx',
    LIBSUFFIXES=['.ocx'],
)

Когда переназначение происходит таким способом, ключевые аргументы Python в вызовах сборщика означают "установить в это значение". Если нужно, чтобы переназначение увеличивало существующее значение, то необходимо предпринять дополнительные шаги. Внутри вызова сборщика можно заменить существующее значение путем использования строки, содержащей имя переменной с префиксом символа доллара ($).

env = Environment(CPPDEFINES="FOO")
env.Object(target="foo1.o", source="foo.c")
env.Object(target="foo2.o", source="foo.c", CPPDEFINES="BAR")
env.Object(target="foo3.o", source="foo.c", CPPDEFINES=["BAR", "$CPPDEFINES"])

Результат сборки:

% scons -Q
cc -o foo1.o -c -DFOO foo.c
cc -o foo2.o -c -DBAR foo.c
cc -o foo3.o -c -DBAR -DFOO foo.c

В переопределении можно использовать ключевой аргумент parse_flags для слияния аргументов в стиле командной строки в соответствующие construction-переменные. Это работает наподобие метода env.MergeFlags, который будет полностью рассмотрен в следующей главе.

Этот пример добавит 'include' к $CPPPATH, 'EBUG' к $CPPDEFINES, и 'm' к $LIBS:

env = Environment()
env.Program('hello', 'hello.c', parse_flags='-Iinclude -DEBUG -lm')

Результат компиляции:

% scons -Q
cc -o hello.o -c -DEBUG -Iinclude hello.c
cc -o hello hello.o -lm

Использование временных переназначений это способ упростить создание сценариев сборки в сравнении с созданием полного construction-окружения, что может повысить производительность в больших проектах, где есть много специальных значений для установки. Однако нужно учесть, что это хорошо работает только когда target-ы уникальные. Использование переназначений для сборщика в попытке собрать ту же самую target с различными наборами флагов или других construction-переменных приведет к ошибке scons: *** Two environments with different actions..., которая была описана выше в секции "Несколько construction-окружений". В этом случае лучше создать отдельные окружения.

[Управление Execution-окружением для выданных команд]

Когда SCons выполняет сборку target-файла, она не выполняет команды в external environment, который используется для выполнения SCons. Вместо этого SCons собирает execution environment из значений, сохраненных в construction-переменной $ENV, и использует их для выполнения команд.

Наиболее важным дополнением это поведения является то, что переменная окружения PATH, которая управляет поиском операционной системы запускаемых команд и утилит, почти наверняка не будет такой же, как в external environment, из которого вы запустили SCons. Это означает, что необязательно SCons может найти инструменты, которые вы успешно запустили из командной строки.

Значение по умолчанию переменной окружения PATH на системе POSIX это /usr/local/bin:/opt/bin:/bin:/usr/bin:/snap/bin. Значение по умолчанию переменной окружения PATH на Windows берется из значения реестра для интерпретатора команд CMD.EXE. Если вы заходите выполнить любые команды (компиляторы, линкеры, и т. п.), которые не находятся в этих путях по умолчанию, то необходимо установить значение PATH словаря $ENV в вашем construction-окружении.

Самый простой способ сделать это - явно инициализировать значение, когда вы создаете construction environment. Для такого действия есть единственный метод:

path = ['/usr/local/bin', '/bin', '/usr/bin']
env = Environment(ENV={'PATH': path})

Назначение таким методом словаря для construction-переменной $ENV полностью сбросит execution environment, так что единственной переменной, которая будет установлена, когда выполняются внешние команды, будет значение PATH. Если вы хотите использовать остальные значения в $ENV, и установили только значение PATH, то можно назначить значение только этой переменной:

env['ENV']['PATH'] = ['/usr/local/bin', '/bin', '/usr/bin']

Обратите внимание, что SCons позволяет определить директории в PATH в виде строки, где отдельные директории разделяются специальным символом разделителя путей (':' на системах POSIX, ';' на Windows).

env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin'

Однако такой способ установки PATH делает файл SConscript менее портируемым, поскольку будет корректным только для тех систем, где разделитель в строке соответствует установленному в системе. Вы можете использовать Python-функцию os.pathsep для улучшения портируемости - не слишком беспокойтесь об этом синтаксисе Python, поскольку есть и другие методы:

import os
 
env['ENV']['PATH'] = os.pathsep.join(['/usr/local/bin', '/bin', '/usr/bin'])

Наследование PATH из External-окружения. Вы можете распространить внешнюю переменную PATH на execution-окружение для команд. Это делается путем инициализации переменной PATH значением PATH из словаря os.environ:

import os
 
env = Environment(ENV={'PATH': os.environ['PATH']})

Альтернативно вам может показаться проще распространить весь external environment на execution environment для команд. Код для этого проще, чем явный выбор значения PATH:

import os
 
env = Environment(ENV=os.environ.copy())

Любой из этих вариантов обеспечит, что SCons сможет выполнить любую команду командной строки. Недостаток такой конфигурации в том, что на разных машинах могут быть разные значения PATH в рабочем окружении - например, если обе директории /bin и /usr/local/bin содержат разные команды cc, то какая из них будет использоваться будет зависеть от того, какая из директорий идет первой среди путей переменной PATH.

Добавление значений к PATH в Execution-окружении. Одно из наиболее общих требований для манипуляции над переменной в execution environment - возможность добавления одной или большего количества директорий к путям поиска в переменной $PATH на Linux/POSIX системах, или %PATH% на Windows, так чтобы локально установленный компилятор или другая утилита могла быть найдена, когда SCons пытается выполнить их для обновления target. Для удобства SCons предоставляет функции env.PrependENVPath и env.AppendENVPath, чтобы делать добавления спереди и сзади к путям поиска запускаемых файлов. Вы можете вызывать эти функции путем указания переменной, в которую вы хотите добавить значение, и затем само добавляемое значение. Например, для добавления некоторых директорий /usr/local в переменные $PATH и $LIB, можно использовать такой код:

env = Environment(ENV=os.environ.copy())
env.PrependENVPath('PATH', '/usr/local/bin')
env.AppendENVPath('LIB', '/usr/local/lib')

Обратите внимание, что добавляемые значения это строки, и если нужно добавить несколько директорий к переменной наподобие $PATH, то в строке с несколькими директориями нужно использовать символ разделителя (: на Linux/POSIX, ; на Windows, или для портируемости использовать os.pathsep).

[Использование toolpath для внешних инструментов]

Путь по умолчанию для поиска инструментов. Обычно, когда используется инструмент из construction-окружения, по умолчанию проверяется несколько разных мест для поиска. Эти места включают директорию SCons/Tools/, которая является частью дистрибутива scons, и директорию site_scons/site_tools относительно корневой папки файла SConstruct.

# Встроенный инструмент, или инструмент, находящийся в папке site_tools
env = Environment(tools=['SomeTool'])
env.SomeTool(targets, sources)
 
# Места поиска будут включать по умолчанию
SCons/Tool/SomeTool.py
SCons/Tool/SomeTool/__init__.py
./site_scons/site_tools/SomeTool.py
./site_scons/site_tools/SomeTool/__init__.py

Предоставление внешней директории для toolpath. В некоторых случаях вы можете захотеть указать другое место поиска для поиска запускаемых утилит. Функция содержит для этого опцию toolpath, которая может использоваться для добавления дополнительных директорий поиска.

# Инструмент находится в опции директории toolpath
env = Environment(
    tools=['SomeTool'],
    toolpath=['/opt/SomeToolPath', '/opt/SomeToolPath2']
)
env.SomeTool(targets, sources)
 
# Места поиска в этом примере включают в себя:
/opt/SomeToolPath/SomeTool.py
/opt/SomeToolPath/SomeTool/__init__.py
/opt/SomeToolPath2/SomeTool.py
/opt/SomeToolPath2/SomeTool/__init__.py
SCons/Tool/SomeTool.py
SCons/Tool/SomeTool/__init__.py
./site_scons/site_tools/SomeTool.py
./site_scons/site_tools/SomeTool/__init__.py

Вложенные инструменты в toolpath. Начиная с версии SCons 3.0 сборщик может находиться в подкаталоге пути toolpath. Это аналогично пространству имен в Python. С вложенными namespaced-инструментами мы можем использовать нотацию точки для указания подкаталога, в котором находится запускаемый инструмент.

# namespaced target
env = Environment(
    tools=['SubDir1.SubDir2.SomeTool'],
    toolpath=['/opt/SomeToolPath']
)
env.SomeTool(targets, sources)
 
# С этим примером места поиска будут включать:
/opt/SomeToolPath/SubDir1/SubDir2/SomeTool.py
/opt/SomeToolPath/SubDir1/SubDir2/SomeTool/__init__.py
SCons/Tool/SubDir1/SubDir2/SomeTool.py
SCons/Tool/SubDir1/SubDir2/SomeTool/__init__.py
./site_scons/site_tools/SubDir1/SubDir2/SomeTool.py
./site_scons/site_tools/SubDir1/SubDir2/SomeTool/__init__.py

Использование sys.path в toolpath. Если мы хотим получить доступ к инструментам, внешним для scons, которые ищутся через sys.path (например, инструменты установлены через Python-менеджер пакетов pip), то можно использовать sys.path с toolpath. При этом нужно следить за тем, что sys.path может иногда содержать пути до файлов .egg вместо директорий. Так что при таком подходе нам понадобится это отфильтровать.

# namespaced target с использованием sys.path с toolpath 
searchpaths = []
for item in sys.path:
    if os.path.isdir(item):
        searchpaths.append(item)
 
env = Environment(
    tools=['someinstalledpackage.SomeTool'],
    toolpath=searchpaths
)
env.SomeTool(targets, sources)

С использованием sys.path с аргументом toolpath и при использовании nested-синтаксиса мы можем установить пути поиска пакетов scons, установленные через pip для Tools.

Для Windows на основе версии python и директории установки это может быть что-то наподобие:

C:\Python35\Lib\site-packages\someinstalledpackage\SomeTool.py
C:\Python35\Lib\site-packages\someinstalledpackage\SomeTool\__init__.py

Для Linux может быть так:

/usr/lib/python3/dist-packages/someinstalledpackage/SomeTool.py
/usr/lib/python3/dist-packages/someinstalledpackage/SomeTool/__init__.py

Использование функции PyPackageDir для добавления toolpath. В некоторых случаях может потребоваться использовать инструмент, расположенный в установленном внешнем пакете pip. Это возможно с использованием sys.path с toolpath. Однако в такой ситуации нужно предоставить префикс для имени инструмента, чтобы показать, где он находится в sys.path.

searchpaths = []
for item in sys.path:
    if os.path.isdir(item):
        searchpaths.append(item)
env = Environment(
    tools=['tools_example.subdir1.subdir2.SomeTool'],
    toolpath=searchpaths
)
env.SomeTool(targets, sources)

Чтобы избежать использования префикса с именем инструмента или фильтрации sys.path для директорий, мы можем использовать функцию PyPackageDir для обнаружения директории пакета python. PyPackageDir вернет объект Dir, который представляет путь директории для пакета/модуля python, указанный в параметре.

# namespaced target с использованием using sys.path
env = Environment(
    tools=['SomeTool'],
    toolpath=[PyPackageDir('tools_example.subdir1.subdir2')]
)
env.SomeTool(targets, sources)

[Автоматическое включение параметров командной строки в construction-переменные]

В этой главе описываются методы MergeFlags, ParseFlags и ParseConfig в construction-окружении, а также ключевое слово аргумента parse_flags для методов, создающих окружения.

Слияние опций с Environment: функция MergeFlags. У construction-окружений SCons есть метод MergeFlags, который объединяет значения, переданные через аргумент с construction-окружением. Если аргумент это словарь, то MergeFlags обрабатывает каждое значение в словаре как список опций, передаваемых в команду (такую как компилятор или линкер). MergeFlags не дублирует опцию, если она уже присутствует в construction-переменной. Если же аргумент это строка, то MergeFlags вызовет метод ParseFlags, чтобы сначала извлечь это в словарь, и затем применить для результата.

MergeFlags пытается интеллигентно объединять опции, учитывая тот факт, что разные construction-переменные имеют разные предназначения. Когда объединяются опции с любой переменной, имя которой заканчивается на PATH, MergeFlags сохраняет крайнее левое вхождение опции, потому что в типичных списках путей директорий первый элемент списка "выигрывает". Когда объединяются опции с любым другим именем переменной, MergeFlags сохраняет крайнее правое вхождение опции, потому что в типовой командной строке "выигрывает" последняя опция.

env = Environment()
env.Append(CCFLAGS='-option -O3 -O1')
flags = {'CCFLAGS': '-whatever -O3'}
env.MergeFlags(flags)print("CCFLAGS:", env['CCFLAGS'])

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

% scons -Q
CCFLAGS: ['-option', '-O1', '-whatever', '-O3']
scons: `.' is up to date.

Обратите внимание, что значением по умолчанию для $CCFLAGS является внутренний объект SCons, который автоматически преобразует указанные в виде строки опции в список.

env = Environment()
env.Append(CPPPATH=['/include', '/usr/local/include', '/usr/include'])
flags = {'CPPPATH': ['/usr/opt/include', '/usr/local/include']}
env.MergeFlags(flags)print("CPPPATH:", env['CPPPATH'])

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

% scons -Q
CPPPATH: ['/include', '/usr/local/include', '/usr/include', '/usr/opt/include']
scons: `.' is up to date.

Обратите внимание, что значение по умолчанию для $CPPPATH это нормальный список Python, поэтому следует задать его значения как список в словаре, передаваемом функции MergeFlags.

Если MergeFlags передано что-то, отличное от словаря, то MergeFlags вызовет метод ParseFlags для преобразования переданного в словарь.

env = Environment()
env.Append(CCFLAGS='-option -O3 -O1')
env.Append(CPPPATH=['/include', '/usr/local/include', '/usr/include'])
env.MergeFlags('-whatever -I/usr/opt/include -O3 -I/usr/local/include')
print("CCFLAGS:", env['CCFLAGS'])
print("CPPPATH:", env['CPPPATH'])

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

% scons -Q
CCFLAGS: ['-option', '-O1', '-whatever', '-O3']
CPPPATH: ['/include', '/usr/local/include', '/usr/include', '/usr/opt/include']
scons: `.' is up to date.

В примере выше ParseFlags сортирует опции в соответствующие переменные, и возвращает словарь для MergeFlags, чтобы применить к construction-переменным в указанном construction-окружении.

Объединение опций при создании Environment: параметр parse_flags. Также можно объединить значения construction-переменной из аргументов, заданных для самого вызова Environment. Если предоставлен аргумент с ключевым словом parse_flags, то его значение распространяется на construction-переменные в новом environment таким же образом, как это описано для метода MergeFlags. Это также работает, когда вызывается env.Clone, а также в переопределениях методов сборщика (см. выше секцию "Переустановка значения construction-переменной").

env = Environment(parse_flags="-I/opt/include -L/opt/lib -lfoo")
for k in ('CPPPATH', 'LIBPATH', 'LIBS'):
    print("%s:" % k, env.get(k))
env.Program("f1.c")

Запуск:

% scons -Q
CPPPATH: ['/opt/include']
LIBPATH: ['/opt/lib']
LIBS: ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo

Разделение аргументов компиляции на их переменные: функция ParseFlags. Когда собирается ПО, у SCons имеется массив construction-переменных для различных типов опций. Иногда вы можете не знать точно, какая переменная должна быть использована для определенной опции.

У Construction-окружений SCons есть метод ParseFlags, который берет набор типовых опций командной строки, и переводит из в соответствующие construction-переменные. Исторически это было создано для поддержки метода ParseConfig, поэтому ParseFlags фокусируется на опциях, используемых GNU Compiler Collection (GCC) для тулчейнов C и C++.

ParseFlags возвратит словарь, содержащий опции, распространенные в их соответствующие construction-переменные. Обычно этот словарь передается затем в MergeFlags, чтобы объединить опции в construction-окружение, однако словарь может быть отредактирован, если нужно предоставить дополнительный функционал. Обратите внимание, что если флаги не надо редактировать, то вызов MergeFlags напрямую с опциями позволяет избежать этого дополнительного шага.

env = Environment()
d = env.ParseFlags("-I/opt/include -L/opt/lib -lfoo")
for k, v in sorted(d.items()):
    if v:
        print(k, v)
env.MergeFlags(d)
env.Program("f1.c")

Запуск:

% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo

Обратите внимание, что если опции ограничены универсальными типами, наподобие показанных выше, то они будут корректно транслированы для других типов платформ разработки:

C:\>scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cl /Fof1.obj /c f1.c /nologo /I\opt\include
link /nologo /OUT:f1.exe /LIBPATH:\opt\lib foo.lib f1.obj
embedManifestExeCheck(target, source, env)

Поскольку предполагается, что флаги используются для тулчейна GCC, нераспознанные флаги помещаются в $CCFLAGS, так что они будут использоваться для обоих компиляторов C и C++:

env = Environment()
d = env.ParseFlags("-whatever")
for k, v in sorted(d.items()):
    if v:
        print(k, v)
env.MergeFlags(d)
env.Program("f1.c")

Запуск:

% scons -Q
CCFLAGS -whatever
cc -o f1.o -c -whatever f1.c
cc -o f1 f1.o

ParseFlags будет также на входе принимать (рекурсивно) список строк; перед обработкой строк список делается плоским:

env = Environment()
d = env.ParseFlags(["-I/opt/include", ["-L/opt/lib", "-lfoo"]])
for k, v in sorted(d.items()):
    if v:
        print(k, v)
env.MergeFlags(d)
env.Program("f1.c")

Запуск:

% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo

Если строки начинаются с символа восклицательного знака (!), то строка передается в shell для выполнения. Затем вывод команды парсится:

env = Environment()
d = env.ParseFlags(["!echo -I/opt/include", "!echo -L/opt/lib", "-lfoo"])
for k, v in sorted(d.items()):
    if v:
        print(k, v)
env.MergeFlags(d)
env.Program("f1.c")

Запуск:

% scons -Q
CPPPATH ['/opt/include']
LIBPATH ['/opt/lib']
LIBS ['foo']
cc -o f1.o -c -I/opt/include f1.c
cc -o f1 f1.o -L/opt/lib -lfoo

Функция ParseFlags регулярно обновляется для новых опций; проконсультируйтесь со страничкой справки man для получения подробностей о текущих распознаваемых опциях.

Поиск информации по установленной библиотеке: функция ParseConfig. Конфигурирование правильных опций для сборки программ для работы с библиотеками - особенно для shared-библиотек - которые доступны на POSIX-системах, может быть сложной задачей. Для помощи в такой ситуации различные утилиты, имена которых заканчиваются на config, возвращают опции командной строки для GNU Compiler Collection (GCC), которые необходимы для сборки и линковки этих библиотек. Например, опции командной строки для использования библиотеки lib можно найти, вызвав утилиту с именем lib-config.

Более свежим соглашением определяется, что эти опции доступны через общую программу pkg-config, предоставляющую общий фреймворк, обработку ошибок т. п., так что все, что должен сделать создатель пакета, это предоставить набор сброк для конкретного пакета.

У construction-переменных SCons есть метод ParseConfig, который запрашивает систему хоста выполнение команды, и затем конфигурирует подходящие construction-переменные на основе вывода этой команды. Это позволяет запустить программу наподобие pkg-config или более специфичную утилиту, чтобы помочь в настройке вашей сборки.

env = Environment()
env['CPPPATH'] = ['/lib/compat']
env.ParseConfig("pkg-config x11 --cflags --libs")
print("CPPPATH:", env['CPPPATH'])

SCons выполнит указанную строку команды, выполнит парсинг результирующих флагов, и добавит флаги в соответствующие переменные окружения.

% scons -Q
CPPPATH: ['/lib/compat', '/usr/X11/include']
scons: `.' is up to date.

В примере выше SCons добавила каталог include к $CPPPATH (в зависимости от того, какие флаги выдаются командой pkg-config, могут быть расширены также и другие переменные).

Обратите внимание, что опции объединены с существующими опциями с использованием метода MergeFlags, так что в construction-переменной каждая опция появляется только один раз.

env = Environment()
env.ParseConfig("pkg-config x11 --cflags --libs")
env.ParseConfig("pkg-config x11 --cflags --libs")
print("CPPPATH:", "CPPPATH:", env['CPPPATH'])

Запуск:

% scons -Q
CPPPATH: ['/usr/X11/include']
scons: `.' is up to date.

[Ссылки]

1. SCons: руководство пользователя, быстрый старт.
2SCons: управление выводом процесса сборки.