%scons; %builders-mod; %functions-mod; %tools-mod; %variables-mod; ]> Environments An environment is a collection of values that can affect how a program executes. &SCons; distinguishes between three different types of environments that can affect the behavior of &SCons; itself (subject to the configuration in the &SConscript; files), as well as the compilers and other tools it executes: External Environment The External Environment is the set of variables in the user's environment at the time the user runs &SCons;. These variables are not automatically part of an &SCons; build but are available to be examined if needed. See , below. Construction Environment A &ConsEnv; is a distinct object created within a &SConscript; file and which contains values that affect how &SCons; decides what action to use to build a target, and even to define which targets should be built from which sources. One of the most powerful features of &SCons; is the ability to create multiple &consenvs;, including the ability to clone a new, customized &consenv; from an existing &consenv;. See , below. Execution Environment An Execution Environment is the values that &SCons; sets when executing an external command (such as a compiler or linker) to build one or more targets. Note that this is not the same as the external environment (see above). See , below. Unlike &Make;, &SCons; does not automatically copy or import values between different environments (with the exception of explicit clones of &consenvs;, which inherit the values from their parent). This is a deliberate design choice to make sure that builds are, by default, repeatable regardless of the values in the user's external environment. This avoids a whole class of problems with builds where a developer's local build works because a custom variable setting causes a different compiler or build option to be used, but the checked-in change breaks the official build because it uses different environment variable settings. Note that the &SConscript; writer can easily arrange for variables to be copied or imported between environments, and this is often very useful (or even downright necessary) to make it easy for developers to customize the build in appropriate ways. The point is not that copying variables between different environments is evil and must always be avoided. Instead, it should be up to the implementer of the build system to make conscious choices about how and when to import a variable from one environment to another, making informed decisions about striking the right balance between making the build repeatable on the one hand and convenient to use on the other. Sidebar: Python Dictionaries If you're not familiar with the &Python; programming language, we need to talk a little bit about the &Python; dictionary data type. A dictionary (also known by terms such as mapping, associative array and key-value store) associates keys with values, such that asking the dict about a key gives you back the associated value and assigning to a key creates the association - either a new setting if the key was unknown, or replacing the previous association if the key was already in the dictionary. Values can be retrieved using item access (the key name in square brackets ([])), and dictionaries also provide a method named get which responds with a default value, either None or a value you supply as the second argument, if the key is not in the dictionary, which avoids failing in that case. The syntax for initializing a dictionary uses curly braces ({}). Here are some simple examples (inspired by those in the official Python tutorial) using syntax that indicates interacting with the &Python; interpreter (>>> is the interpreter prompt) - you can try these out: >>> 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 &Consenvs; are written to behave like a &Python; dictionary, and the &cv-ENV; construction variable in a &consenv; is a &Python; dictionary. The os.environ value that &Python; uses to make available the external environment is also a dictionary. We will need these concepts in this chapter and throughout the rest of this guide.
Using Values From the External Environment The external environment variable settings that the user has in force when executing &SCons; are available in the &Python; os.environ dictionary. That syntax means the environ attribute of the os module. In Python, to access the contents of a module you must first import it - so you would include the import os statement to any &SConscript; file in which you want to use values from the user's external environment. import os print("Shell is", os.environ['SHELL']) void main() { } More usefully, you can use the os.environ dictionary in your &SConscript; files to initialize &consenvs; with values from the user's external environment. Read on to the next section for information on how to do this.
Construction Environments It is rare that all of the software in a large, complicated system needs to be built exactly the same way. For example, different source files may need different options enabled on the command line, or different executable programs need to be linked with different libraries. &SCons; accommodates these different build requirements by allowing you to create and configure multiple &consenvs; that control how the software is built. A &consenv; is an object that has a number of associated &consvars;, each with a name and a value, just like a dictionary. (A construction environment also has an attached set of &Builder; methods, about which we'll learn more later.)
Creating a &ConsEnv;: the &Environment; Function A &consenv; is created by the &Environment; method: env = Environment() By default, &SCons; initializes every new construction environment with a set of &consvars; based on the tools that it finds on your system, plus the default set of builder methods necessary for using those tools. The construction variables are initialized with values describing the C compiler, the Fortran compiler, the linker, etc., as well as the command lines to invoke them. When you initialize a construction environment you can set the values of the environment's &consvars; to control how a program is built. For example: env = Environment(CC='gcc', CCFLAGS='-O2') env.Program('foo.c') int main() { } The construction environment in this example is still initialized with the same default construction variable values, except that the user has explicitly specified use of the GNU C compiler &gcc;, and that the (optimization level two) flag should be used when compiling the object file. In other words, the explicit initializations of &cv-link-CC; and &cv-link-CCFLAGS; override the default values in the newly-created construction environment. So a run from this example would look like: scons -Q
Fetching Values From a &ConsEnv; You can fetch individual values, known as Construction Variables, using the same syntax used for accessing individual named items in a &Python; dictionary: env = Environment() print("CC is: %s" % env['CC']) print("LATEX is: %s" % env.get('LATEX', None)) This example &SConstruct; file doesn't contain instructions for building any targets, but because it's still a valid &SConstruct; it will be evaluated and the &Python; print calls will output the values of &cv-link-CC; and &cv-link-LATEX; for us (remember using the .get() method for fetching means we get a default value back, rather than a failure, if the variable is not set): scons -Q A &consenv; is actually an object with associated methods and attributes. If you want to have direct access to only the dictionary of &consvars; you can fetch this using the &f-link-env-Dictionary; method (although it's rarely necessary to use this method): env = Environment(FOO='foo', BAR='bar') cvars = env.Dictionary() for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']: print("key = %s, value = %s" % (key, cvars[key])) This &SConstruct; file will print the specified dictionary items for us on POSIX systems as follows: scons -Q And on Windows: scons -Q If you want to loop and print the values of all of the &consvars; in a &consenv;, the &Python; code to do that in sorted order might look something like: env = Environment() for item in sorted(env.Dictionary().items()): print("construction variable = '%s', value = '%s'" % item) It should be noted that for the previous example, there is actually a &consenv; method that does the same thing more simply, and tries to format the output nicely as well: env = Environment() print(env.Dump())
Expanding Values From a &ConsEnv;: the &subst; Method Another way to get information from a &consenv; is to use the &subst; method on a string containing $ expansions of &consvar; names. As a simple example, the example from the previous section that used env['CC'] to fetch the value of &cv-link-CC; could also be written as: env = Environment() print("CC is: %s" % env.subst('$CC')) One advantage of using &subst; to expand strings is that &consvars; in the result get re-expanded until there are no expansions left in the string. So a simple fetch of a value like &cv-link-CCCOM;: env = Environment(CCFLAGS='-DFOO') print("CCCOM is: %s" % env['CCCOM']) Will print the unexpanded value of &cv-CCCOM;, showing us the construction variables that still need to be expanded: % scons -Q CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES scons: `.' is up to date. Calling the &subst; method on $CCOM, however: env = Environment(CCFLAGS='-DFOO') print("CCCOM is: %s" % env.subst('$CCCOM')) Will recursively expand all of the construction variables prefixed with $ (dollar signs), showing us the final output: % scons -Q CCCOM is: gcc -DFOO -c -o scons: `.' is up to date. Note that because we're not expanding this in the context of building something there are no target or source files for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
Handling Problems With Value Expansion If a problem occurs when expanding a construction variable, by default it is expanded to '' (an empty string), and will not cause &scons; to fail. env = Environment() print("value is: %s"%env.subst( '->$MISSING<-' )) scons -Q This default behaviour can be changed using the &AllowSubstExceptions; function. When a problem occurs with a variable expansion it generates an exception, and the &AllowSubstExceptions; function controls which of these exceptions are actually fatal and which are allowed to occur safely. By default, &NameError; and &IndexError; are the two exceptions that are allowed to occur: so instead of causing &scons; to fail, these are caught, the variable expanded to '' and &scons; execution continues. To require that all construction variable names exist, and that indexes out of range are not allowed, call &AllowSubstExceptions; with no extra arguments. AllowSubstExceptions() env = Environment() print("value is: %s"%env.subst( '->$MISSING<-' )) scons -Q This can also be used to allow other exceptions that might occur, most usefully with the ${...} construction variable syntax. For example, this would allow zero-division to occur in a variable expansion in addition to the default exceptions allowed AllowSubstExceptions(IndexError, NameError, ZeroDivisionError) env = Environment() print("value is: %s"%env.subst( '->${1 / 0}<-' )) scons -Q If &AllowSubstExceptions; is called multiple times, each call completely overwrites the previous list of allowed exceptions.
Controlling the Default &ConsEnv;: the &DefaultEnvironment; Function All of the &Builder; functions that we've introduced so far, like &Program; and &Library;, use a &consenv; that contains settings for the various compilers and other tools that &SCons; configures by default, or otherwise knows about and has discovered on your system. If not invoked as methods of a specific &consenv;, they use the default &consenv; The goal of the default &consenv; is to make many configurations "just work" to build software using readily available tools with a minimum of configuration changes. If needed, you can control the default &consenv; by using the &DefaultEnvironment; function to initialize various settings by passing them as keyword arguments: DefaultEnvironment(CC='/usr/local/bin/gcc') When configured as above, all calls to the &Program; or &Object; Builder will build object files with the /usr/local/bin/gcc compiler. The &DefaultEnvironment; function returns the initialized default &consenv; object, which can then be manipulated like any other &consenv; (note that the default environment works like a singleton - it can have only one instance - so the keyword arguments are processed only on the first call. On any subsequent call the existing object is returned). So the following would be equivalent to the previous example, setting the &cv-CC; variable to /usr/local/bin/gcc but as a separate step after the default construction environment has been initialized: def_env = DefaultEnvironment() def_env['CC'] = '/usr/local/bin/gcc' One very common use of the &DefaultEnvironment; function is to speed up &SCons; initialization. As part of trying to make most default configurations "just work," &SCons; will actually search the local system for installed compilers and other utilities. This search can take time, especially on systems with slow or networked file systems. If you know which compiler(s) and/or other utilities you want to configure, you can control the search that &SCons; performs by specifying some specific tool modules with which to initialize the default construction environment: def_env = DefaultEnvironment(tools=['gcc', 'gnulink'], CC='/usr/local/bin/gcc') So the above example would tell &SCons; to explicitly configure the default environment to use its normal GNU Compiler and GNU Linker settings (without having to search for them, or any other utilities for that matter), and specifically to use the compiler found at /usr/local/bin/gcc.
Multiple &ConsEnvs; The real advantage of &consenvs; is that you can create as many different ones as you need, each tailored to a different way to build some piece of software or other file. If, for example, we need to build one program with the flag and another with the (debug) flag, we would do this like so: opt = Environment(CCFLAGS='-O2') dbg = Environment(CCFLAGS='-g') opt.Program('foo', 'foo.c') dbg.Program('bar', 'bar.c') int main() { } int main() { } scons -Q We can even use multiple &consenvs; to build multiple versions of a single program. If you do this by simply trying to use the &b-link-Program; builder with both environments, though, like this: opt = Environment(CCFLAGS='-O2') dbg = Environment(CCFLAGS='-g') opt.Program('foo', 'foo.c') dbg.Program('foo', 'foo.c') int main() { } Then &SCons; generates the following error: scons -Q This is because the two &b-Program; calls have each implicitly told &SCons; to generate an object file named foo.o, one with a &cv-link-CCFLAGS; value of and one with a &cv-link-CCFLAGS; value of . &SCons; can't just decide that one of them should take precedence over the other, so it generates the error. To avoid this problem, we must explicitly specify that each environment compile foo.c to a separately-named object file using the &b-link-Object; builder, like so: 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) int main() { } Notice that each call to the &b-Object; builder returns a value, an internal &SCons; object that represents the object file that will be built. We then use that object as input to the &b-Program; builder. This avoids having to specify explicitly the object file name in multiple places, and makes for a compact, readable &SConstruct; file. Our &SCons; output then looks like: scons -Q
Making Copies of &ConsEnvs;: the &Clone; Method Sometimes you want more than one construction environment to share the same values for one or more variables. Rather than always having to repeat all of the common variables when you create each construction environment, you can use the &f-link-env-Clone; method to create a copy of a construction environment. Like the &f-link-Environment; call that creates a construction environment, the &Clone; method takes &consvar; assignments, which will override the values in the copied construction environment. For example, suppose we want to use &gcc; to create three versions of a program, one optimized, one debug, and one with neither. We could do this by creating a "base" construction environment that sets &cv-link-CC; to &gcc;, and then creating two copies, one which sets &cv-link-CCFLAGS; for optimization and the other which sets &cv-CCFLAGS; for debugging: 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) int main() { } Then our output would look like: scons -Q
Replacing Values: the &Replace; Method You can replace existing construction variable values using the &f-link-env-Replace; method: env = Environment(CCFLAGS='-DDEFINE1') env.Replace(CCFLAGS='-DDEFINE2') env.Program('foo.c') int main() { } The replacing value (-DDEFINE2 in the above example) completely replaces the value in the construction environment: scons -Q You can safely call &Replace; for construction variables that don't exist in the construction environment: env = Environment() env.Replace(NEW_VARIABLE='xyzzy') print("NEW_VARIABLE = %s" % env['NEW_VARIABLE']) In this case, the construction variable simply gets added to the construction environment: scons -Q Because the variables aren't expanded until the construction environment is actually used to build the targets, and because &SCons; function and method calls are order-independent, the last replacement "wins" and is used to build all targets, regardless of the order in which the calls to Replace() are interspersed with calls to builder methods: 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') int main() { } int main() { } The timing of when the replacement actually occurs relative to when the targets get built becomes apparent if we run &scons; without the option: scons Because the replacement occurs while the &SConscript; files are being read, the &cv-link-CCFLAGS; variable has already been set to -DDEFINE2 by the time the &foo_o; target is built, even though the call to the &Replace; method does not occur until later in the &SConscript; file.
Setting Values Only If They're Not Already Defined: the &SetDefault; Method Sometimes it's useful to be able to specify that a construction variable should be set to a value only if the construction environment does not already have that variable defined You can do this with the &f-link-env-SetDefault; method, which behaves similarly to the setdefault method of &Python; dictionary objects: env.SetDefault(SPECIAL_FLAG='-extra-option') This is especially useful when writing your own Tool modules to apply variables to construction environments.
Appending to the End of Values: the &Append; Method You can append a value to an existing construction variable using the &f-link-env-Append; method: env = Environment(CPPDEFINES=['MY_VALUE']) env.Append(CPPDEFINES=['LAST']) env.Program('foo.c') int main() { } Note &cv-link-CPPDEFINES; is the preferred way to set preprocessor defines, as &SCons; will generate the command line arguments using the correct prefix/suffix for the platform, leaving the usage portable. If you use &cv-link-CCFLAGS; and &cv-link-SHCCFLAGS;, you need to include them in their final form, which is less portable. scons -Q If the construction variable doesn't already exist, the &Append; method will create it: env = Environment() env.Append(NEW_VARIABLE = 'added') print("NEW_VARIABLE = %s"%env['NEW_VARIABLE']) Which yields: scons -Q Note that the &Append; function tries to be "smart" about how the new value is appended to the old value. If both are strings, the previous and new strings are simply concatenated. Similarly, if both are lists, the lists are concatenated. If, however, one is a string and the other is a list, the string is added as a new element to the list.
Appending Unique Values: the &AppendUnique; Method Sometimes it's useful to add a new value only if the existing construction variable doesn't already contain the value. This can be done using the &f-link-env-AppendUnique; method: env.AppendUnique(CCFLAGS=['-g']) In the above example, the -g would be added only if the &cv-CCFLAGS; variable does not already contain a -g value.
Prepending to the Beginning of Values: the &Prepend; Method You can prepend a value to the beginning of an existing construction variable using the &f-link-env-Prepend; method: env = Environment(CPPDEFINES=['MY_VALUE']) env.Prepend(CPPDEFINES=['FIRST']) env.Program('foo.c') int main() { } &SCons; then generates the preprocessor define arguments from &CPPDEFINES; values with the correct prefix/suffix. For example on Linux or POSIX, the following arguments would be generated: -DFIRST and -DMY_VALUE scons -Q If the construction variable doesn't already exist, the &Prepend; method will create it: env = Environment() env.Prepend(NEW_VARIABLE='added') print("NEW_VARIABLE = %s" % env['NEW_VARIABLE']) Which yields: scons -Q Like the &Append; function, the &Prepend; function tries to be "smart" about how the new value is appended to the old value. If both are strings, the previous and new strings are simply concatenated. Similarly, if both are lists, the lists are concatenated. If, however, one is a string and the other is a list, the string is added as a new element to the list.
Prepending Unique Values: the &PrependUnique; Method Some times it's useful to add a new value to the beginning of a construction variable only if the existing value doesn't already contain the to-be-added value. This can be done using the &f-link-env-PrependUnique; method: env.PrependUnique(CCFLAGS=['-g']) In the above example, the -g would be added only if the &cv-CCFLAGS; variable does not already contain a -g value.
Overriding Construction Variable Settings Rather than creating a cloned &consenv; for specific tasks, you can override or add construction variables when calling a builder method by passing them as keyword arguments. The values of these overridden or added variables will only be in effect when building that target, and will not affect other parts of the build. For example, if you want to add additional libraries for just one program: env.Program('hello', 'hello.c', LIBS=['gl', 'glut']) or generate a shared library with a non-standard suffix: env.SharedLibrary( target='word', source='word.cpp', SHLIBSUFFIX='.ocx', LIBSUFFIXES=['.ocx'], ) When overriding this way, the &Python; keyword arguments in the builder call mean "set to this value". If you want your override to augment an existing value, you have to take some extra steps. Inside the builder call, it is possible to substitute in the existing value by using a string containing the variable name prefaced by a dollar sign ($). 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"]) void main() { } Which yields: scons -Q It is also possible to use the parse_flags keyword argument in an override to merge command-line style arguments into the appropriate construction variables. This works like the &f-link-env-MergeFlags; method, which will be fully described in the next chapter. This example adds 'include' to &cv-link-CPPPATH;, 'EBUG' to &cv-link-CPPDEFINES;, and 'm' to &cv-link-LIBS;: env = Environment() env.Program('hello', 'hello.c', parse_flags='-Iinclude -DEBUG -lm') #include <stdio.h> void main() { printf("Hello, world\n"); } So when executed: scons -Q Using temporary overrides this way is lighter weight than making a full construction environment, so it can help performance in large projects which have lots of special case values to set. However, keep in mind that this only works well when the targets are unique. Using builder overrides to try to build the same target with different sets of flags or other construction variables will lead to the scons: *** Two environments with different actions... error described in above. In this case you will actually want to create separate environments.
Controlling the Execution Environment for Issued Commands When &SCons; builds a target file, it does not execute the commands with the external environment that you used to execute &SCons;. Instead, it builds an execution environment from the values stored in the &cv-link-ENV; &consvar; and uses that for executing commands. The most important ramification of this behavior is that the &PATH; environment variable, which controls where the operating system will look for commands and utilities, will almost certainly not be the same as in the external environment from which you called &SCons;. This means that &SCons; might not necessarily find all of the tools that you can successfully execute from the command line. The default value of the &PATH; environment variable on a POSIX system is /usr/local/bin:/opt/bin:/bin:/usr/bin:/snap/bin. The default value of the &PATH; environment variable on a Windows system comes from the Windows registry value for the command interpreter. If you want to execute any commands--compilers, linkers, etc.--that are not in these default locations, you need to set the &PATH; value in the &cv-ENV; dictionary in your construction environment. The simplest way to do this is to initialize explicitly the value when you create the construction environment; this is one way to do that: path = ['/usr/local/bin', '/bin', '/usr/bin'] env = Environment(ENV={'PATH': path}) Assigning a dictionary to the &cv-ENV; construction variable in this way completely resets the execution environment, so that the only variable that will be set when external commands are executed will be the &PATH; value. If you want to use the rest of the values in &cv-ENV; and only set the value of &PATH;, you can assign a value only to that variable: env['ENV']['PATH'] = ['/usr/local/bin', '/bin', '/usr/bin'] Note that &SCons; does allow you to define the directories in the &PATH; in a string with paths separated by the pathname-separator character for your system (':' on POSIX systems, ';' on Windows). env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin' But doing so makes your &SConscript; file less portable, since it will be correct only for the system type that matches the separator. You can use the &Python; os.pathsep for for greater portability - don't worry too much if this &Python; syntax doesn't make sense since there are other ways available: import os env['ENV']['PATH'] = os.pathsep.join(['/usr/local/bin', '/bin', '/usr/bin'])
Propagating &PATH; From the External Environment You may want to propagate the external environment &PATH; to the execution environment for commands. You do this by initializing the &PATH; variable with the &PATH; value from the os.environ dictionary, which is &Python;'s way of letting you get at the external environment: import os env = Environment(ENV={'PATH': os.environ['PATH']}) Alternatively, you may find it easier to just propagate the entire external environment to the execution environment for commands. This is simpler to code than explicity selecting the &PATH; value: import os env = Environment(ENV=os.environ.copy()) Either of these will guarantee that &SCons; will be able to execute any command that you can execute from the command line. The drawback is that the build can behave differently if it's run by people with different &PATH; values in their environment--for example, if both the /bin and /usr/local/bin directories have different &cc; commands, then which one will be used to compile programs will depend on which directory is listed first in the user's &PATH; variable.
Adding to <varname>PATH</varname> Values in the Execution Environment One of the most common requirements for manipulating a variable in the execution environment is to add one or more custom directories to a path search variable like PATH on Linux or POSIX systems, or %PATH% on Windows, so that a locally-installed compiler or other utility can be found when &SCons; tries to execute it to update a target. &SCons; provides &f-link-env-PrependENVPath; and &f-link-env-AppendENVPath; functions to make adding things to execution variables convenient. You call these functions by specifying the variable to which you want the value added, and then value itself. So to add some /usr/local directories to the $PATH and $LIB variables, you might: env = Environment(ENV=os.environ.copy()) env.PrependENVPath('PATH', '/usr/local/bin') env.AppendENVPath('LIB', '/usr/local/lib') Note that the added values are strings, and if you want to add multiple directories to a variable like $PATH, you must include the path separator character in the string (: on Linux or POSIX, ; on Windows, or use os.pathsep for portability).
Using the toolpath for external Tools
The default tool search path Normally when using a tool from the construction environment, several different search locations are checked by default. This includes the SCons/Tools/ directory that is part of the &scons; distribution and the directory site_scons/site_tools relative to the root &SConstruct; file. # Builtin tool or tool located within site_tools env = Environment(tools=['SomeTool']) env.SomeTool(targets, sources) # The search locations would include by default SCons/Tool/SomeTool.py SCons/Tool/SomeTool/__init__.py ./site_scons/site_tools/SomeTool.py ./site_scons/site_tools/SomeTool/__init__.py
Providing an external directory to toolpath In some cases you may want to specify a different location to search for tools. The &f-link-Environment; function contains an option for this called toolpath This can be used to add additional search directories. # Tool located within the toolpath directory option env = Environment( tools=['SomeTool'], toolpath=['/opt/SomeToolPath', '/opt/SomeToolPath2'] ) env.SomeTool(targets, sources) # The search locations in this example would include: /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
Nested Tools within a toolpath Since &SCons; 3.0, a Builder may be located within a sub-directory / sub-package of the toolpath. This is similar to namespacing within &Python;. With nested or namespaced tools we can use the dot notation to specify a sub-directory that the tool is located under. # namespaced target env = Environment( tools=['SubDir1.SubDir2.SomeTool'], toolpath=['/opt/SomeToolPath'] ) env.SomeTool(targets, sources) # With this example the search locations would include /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
Using sys.path within the toolpath If we want to access tools external to &scons; which are findable via sys.path (for example, tools installed via Python's pip package manager), it is possible to use sys.path with the toolpath. One thing to watch out for with this approach is that sys.path can sometimes contains paths to .egg files instead of directories. So we need to filter those out with this approach. # namespaced target using sys.path within 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) By using sys.path with the toolpath argument and by using the nested syntax we can have &scons; search packages installed via pip for Tools. # For Windows based on the python version and install directory, this may be something like C:\Python35\Lib\site-packages\someinstalledpackage\SomeTool.py C:\Python35\Lib\site-packages\someinstalledpackage\SomeTool\__init__.py # For Linux this could be something like: /usr/lib/python3/dist-packages/someinstalledpackage/SomeTool.py /usr/lib/python3/dist-packages/someinstalledpackage/SomeTool/__init__.py
Using the &PyPackageDir; function to add to the toolpath In some cases you may want to use a tool located within a installed external pip package. This is possible by the use of sys.path with the toolpath. However in that situation you need to provide a prefix to the toolname to indicate where it is located within 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) To avoid the use of a prefix within the name of the tool or filtering sys.path for directories, we can use &f-link-PyPackageDir; function to locate the directory of the python package. &f-PyPackageDir; returns a Dir object which represents the path of the directory for the python package / module specified as a parameter. # namespaced target using sys.path env = Environment( tools=['SomeTool'], toolpath=[PyPackageDir('tools_example.subdir1.subdir2')] ) env.SomeTool(targets, sources)