<?xml version='1.0'?>

<!--
SPDX-FileCopyrightText: Copyright The SCons Foundation (https://scons.org)
SPDX-License-Identifier: MIT
SPDX-FileType: DOCUMENTATION

This file is processed by the bin/SConsDoc.py module.
-->

<!DOCTYPE sconsdoc [
    <!ENTITY % scons SYSTEM "../scons.mod">
    %scons;

    <!ENTITY % builders-mod SYSTEM "../generated/builders.mod">
    %builders-mod;
    <!ENTITY % functions-mod SYSTEM "../generated/functions.mod">
    %functions-mod;
    <!ENTITY % tools-mod SYSTEM "../generated/tools.mod">
    %tools-mod;
    <!ENTITY % variables-mod SYSTEM "../generated/variables.mod">
    %variables-mod;
]>

<chapter id="chap-misc"
         xmlns="http://www.scons.org/dbxsd/v1.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
<title>Miscellaneous Functionality</title>

  <para>

  &SCons; supports a lot of additional functionality
  that doesn't readily fit into the other chapters.

  </para>

  <section id="sect-ensure-python">
  <title>Verifying the Python Version:  the &EnsurePythonVersion; Function</title>

    <para>

    Although the &SCons; code itself will run
    on any 3.x Python version
    (check the release notes for the precise minimum supported release),
    you are free to make use of
    &Python; syntax and modules from later versions
    when writing your &SConscript; files
    or your own local modules.
    If you do this, it's usually helpful to
    configure &SCons; to exit gracefully with an error message
    if it's being run with a version of &Python;
    that simply won't work with your code.
    This is especially true if you're going to use &SCons;
    to build source code that you plan to distribute publicly,
    where you can't be sure of the &Python; version
    that an anonymous remote user might use
    to try to build your software.

    </para>

    <para>

    &SCons; provides an &EnsurePythonVersion; function for this.
    You simply pass it the major and minor versions
    numbers of the version of &Python; you require:

    </para>

    <!--

    TODO:  Figure out how to generate the error message
    regardless of executing Python version by faking out
    the infrastructure in some way.

    <scons_example name="misc_EnsurePythonVersion">
      <file name="SConstruct" printme="1">
EnsurePythonVersion(3, 8)
      </file>
    </scons_example>

    -->

    <sconstruct>
EnsurePythonVersion(3, 8)
    </sconstruct>

    <para>

    And then &SCons; will exit with the following error
    message when a user runs it with an unsupported
    earlier version of &Python;:

    </para>

    <!--

    TODO:  Figure out how to generate the error message
    regardless of executing Python version by faking out
    the infrastructure in some way.

    <scons_output example="misc_EnsurePythonVersion" suffix="1">
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    -->

    <screen>
% <userinput>scons -Q</userinput>
Python 3.7 or greater required, but you have Python 3.6.5
    </screen>

  </section>

  <section id="sect-ensure-scons">
  <title>Verifying the SCons Version:  the &EnsureSConsVersion; Function</title>

    <para>

    You may, of course, write your &SConscript; files
    to use features that were only added in
    recent versions of &SCons;.
    When you publicly distribute software that is built using &SCons;,
    it's helpful to have &SCons;
    verify the version being used and
    exit gracefully with an error message
    if the user's version of &SCons; won't work
    with your &SConscript; files.
    &SCons; provides an &EnsureSConsVersion; function
    that verifies the version of &SCons;
    in the same
    the &EnsurePythonVersion; function
    verifies the version of &Python;,
    by passing in the major and minor versions
    numbers of the version of SCons you require:

    </para>

    <!--

    TODO:  Figure out how to generate the error message
    regardless of executing SCons version by faking out
    the infrastructure in some way.

    <scons_example name="misc_EnsureSConsVersion">
      <file name="SConstruct" printme="1">
EnsureSConsVersion(1, 0)
      </file>
    </scons_example>

    -->

    <sconstruct>
EnsureSConsVersion(1, 0)
    </sconstruct>

    <para>

    And then &SCons; will exit with the following error
    message when a user runs it with an unsupported
    earlier version of &SCons;:

    </para>

    <!--

    TODO:  Figure out how to generate the error message
    regardless of executing SCons version by faking out
    the infrastructure in some way.

    <scons_output example="misc_EnsureSConsVersion" suffix="1">
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    -->

    <screen>
% <userinput>scons -Q</userinput>
SCons 1.0 or greater required, but you have SCons 0.98.5
    </screen>

  </section>

  <section id="sect-get-scons-version">
  <title>Accessing SCons Version:  the &GetSConsVersion; Function</title>

    <para>

    While &EnsureSConsVersion; is acceptable for most cases, there
    are times where the user will want to support multiple SCons versions
    simultaneously. In this scenario, it's beneficial to retrieve version
    information of the currently executing SCons directly. This was previously
    only possible by accessing SCons internals. From  SCons4.8 onwards, it's now possible
    to instead call &GetSConsVersion; to recieve a tuple containing the
    major, minor, and revision values of the current version.

    </para>

    <screen>
if GetSConsVersion() &gt;= (4, 9):
    # Some function got a new argument in 4.9 that we want to take advantage of
    SomeFunc(arg1, arg2, arg3)
else:
    # Can't use the extended syntax, but it doesn't warrant exiting prematurely
    SomeFunc(arg1, arg2)
    </screen>

  </section>

  <section id="sect-scons-exit">
  <title>Explicitly Terminating &SCons; While Reading &SConscript; Files:  the &Exit; Function</title>

    <para>

    &SCons; supports an &Exit; function
    which can be used to terminate &SCons;
    while reading the &SConscript; files,
    usually because you've detected a condition
    under which it doesn't make sense to proceed:

    </para>

    <scons_example name="misc_Exit">
      <file name="SConstruct" printme="1">
if ARGUMENTS.get('FUTURE'):
    print("The FUTURE option is not supported yet!")
    Exit(2)
env = Environment()
env.Program('hello.c')
      </file>
      <file name="hello.c">
hello.c
      </file>
    </scons_example>

    <scons_output example="misc_Exit" suffix="1">
      <scons_output_command>scons -Q FUTURE=1</scons_output_command>
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <para>

    The &Exit; function takes as an argument
    the (numeric) exit status that you want &SCons; to exit with.
    If you don't specify a value,
    the default is to exit with <literal>0</literal>,
    which indicates successful execution.

    </para>

    <para>

    Note that the &Exit; function
    is equivalent to calling the &Python;
    <function>sys.exit</function> function
    (which it actually calls),
    but because &Exit; is a &SCons; function,
    you don't have to import the &Python;
    <literal>sys</literal> module to use it.

    </para>

  </section>

  <section id="sect-find-file">
  <title>Searching for Files:  the &FindFile; Function</title>

    <para>

     The &f-link-FindFile; function searches for a file in a list of directories.
     If there is only one directory, it can be given as a simple string.
     The function returns a File node if a matching file exists,
     or None if no file is found.
     (See the documentation for the &f-link-Glob; function for an alternative way
     of searching for entries in a directory.)

    </para>

    <scons_example name="misc_FindFile1a">
      <file name="SConstruct" printme="1">
# one directory
print("%s"%FindFile('missing', '.'))
t = FindFile('exists', '.')
print("%s %s"%(t.__class__, t))
      </file>
      <file name="exists">
 exists
      </file>
    </scons_example>

    <scons_output example="misc_FindFile1a" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <scons_example name="misc_FindFile1b">
      <file name="SConstruct" printme="1">
# several directories
includes = [ '.', 'include', 'src/include']
headers = [ 'nonesuch.h', 'config.h', 'private.h', 'dist.h']
for hdr in headers:
    print('%-12s: %s'%(hdr, FindFile(hdr, includes)))
      </file>
      <file name="config.h">
exists
      </file>
      <directory name="src"></directory>
      <directory name="src/include"></directory>
      <file name="src/include/private.h">
exists
      </file>
      <directory name="include"></directory>
      <file name="include/dist.h">
exists
      </file>
    </scons_example>

    <scons_output example="misc_FindFile1b" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

   <!-- The man page says this should work, but it fails.
    <para>

     If the 'file' parameter is a list of files,
     a list of File nodes is returned.

    </para>

    <scons_example name="misc_FindFile1c">
      <file name="SConstruct" printme="1">
# several directories
includes = [ '.', 'include', 'src/include']
headers = [ 'nonesuch.h', 'config.h', 'private.h', 'dist.h']
print(FindFile(headers, includes))
      </file>
      <file name="config.h">
exists
      </file>
      <directory name="src"></directory>
      <directory name="src/include"></directory>
      </file>
      <file name="src/include/private.h">
exists
      <directory name="include"></directory>
      </file>
      <file name="include/dist.h">
exists
    </scons_example>

    <scons_output example="misc_FindFile1c" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>
   -->

    <para>

     If the file exists in more than one directory,
     only the first occurrence is returned.

    </para>

    <scons_example name="misc_FindFile1d">
      <file name="SConstruct" printme="1">
print(FindFile('multiple', ['sub1', 'sub2', 'sub3']))
print(FindFile('multiple', ['sub2', 'sub3', 'sub1']))
print(FindFile('multiple', ['sub3', 'sub1', 'sub2']))
      </file>
      <directory name="sub1"></directory>
      <file name="sub1/multiple">
exists
      </file>
      <directory name="sub2"></directory>
      <file name="sub2/multiple">
exists
      </file>
      <directory name="sub3"></directory>
      <file name="sub3/multiple">
exists
      </file>
    </scons_example>

    <scons_output example="misc_FindFile1d" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

   <!-- file may be a list of file names or a single file name. -->

    <para>

    In addition to existing files, &FindFile; will also find derived files
    (that is, non-leaf files) that haven't been built yet.
    (Leaf files should already exist, or the build will fail!)

    </para>

    <scons_example name="misc_FindFile2">
      <file name="SConstruct" printme="1">
# Neither file exists, so build will fail
Command('derived', 'leaf', 'cat >$TARGET $SOURCE')
print(FindFile('leaf', '.'))
print(FindFile('derived', '.'))
      </file>
    </scons_example>

    <scons_output example="misc_FindFile2" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <scons_example name="misc_FindFile2">
      <file name="SConstruct" printme="1">
# Only 'leaf' exists
Command('derived', 'leaf', 'cat >$TARGET $SOURCE')
print(FindFile('leaf', '.'))
print(FindFile('derived', '.'))
      </file>
      <file name="leaf">
leaf
      </file>
    </scons_example>

    <scons_output example="misc_FindFile2" os="posix" suffix="2">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <para>

    If a source file exists, &FindFile; will correctly return the name
    in the build directory.

    </para>

    <scons_example name="misc_FindFile3">
      <file name="SConstruct" printme="1">
# Only 'src/leaf' exists
VariantDir('build', 'src')
print(FindFile('leaf', 'build'))
      </file>
      <directory name="src"></directory>
      <file name="src/leaf">
leaf
      </file>
    </scons_example>

    <scons_output example="misc_FindFile3" os="posix" suffix="1">
       <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

  </section>

  <section id="sect-flatten-sequence">
  <title>Handling Nested Lists:  the &Flatten; Function</title>

    <para>

    &SCons; supports a &Flatten; function
    which takes an input &Python; sequence
    (list or tuple)
    and returns a flattened list
    containing just the individual elements of
    the sequence.
    This can be handy when trying to examine
    a list composed of the lists
    returned by calls to various Builders.
    For example, you might collect
    object files built in different ways
    into one call to the &Program; Builder
    by just enclosing them in a list, as follows:

    </para>

    <scons_example name="misc_Flatten1">
      <file name="SConstruct" printme="1">
objects = [
    Object('prog1.c'),
    Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)
      </file>
      <file name="prog1.c">
prog1.c
      </file>
      <file name="prog2.c">
prog2.c
      </file>
    </scons_example>

    <para>

    Because the Builder calls in &SCons;
    flatten their input lists,
    this works just fine to build the program:

    </para>

    <scons_output example="misc_Flatten1" suffix="1">
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <para>

    But if you were debugging your build
    and wanted to print the absolute path
    of each object file in the
    <varname>objects</varname> list,
    you might try the following simple approach,
    trying to print each Node's
    <literal>abspath</literal>
    attribute:

    </para>

    <scons_example name="misc_Flatten2">
      <file name="SConstruct" printme="1">
objects = [
    Object('prog1.c'),
    Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)

for object_file in objects:
    print(object_file.abspath)
      </file>
      <file name="prog1.c">
prog1.c
      </file>
      <file name="prog2.c">
prog2.c
      </file>
    </scons_example>

    <para>

    This does not work as expected
    because each call to <function>str</function>
    is operating an embedded list returned by
    each &Object; call,
    not on the underlying Nodes within those lists:

    </para>

    <scons_output example="misc_Flatten2" suffix="1">
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    <para>

    The solution is to use the &Flatten; function
    so that you can pass each Node to
    the <function>str</function> separately:

    </para>

    <scons_example name="misc_Flatten3">
      <file name="SConstruct" printme="1">
objects = [
    Object('prog1.c'),
    Object('prog2.c', CCFLAGS='-DFOO'),
]
Program(objects)

for object_file in Flatten(objects):
    print(object_file.abspath)
      </file>
      <file name="prog1.c">
prog1.c
      </file>
      <file name="prog2.c">
prog2.c
      </file>
    </scons_example>

    <!--

    TODO:  can't use this now because it displays the temporary path name

    <scons_output example="misc_Flatten3" suffix="1">
      <scons_output_command>scons -Q</scons_output_command>
    </scons_output>

    -->

    <screen>
% <userinput>scons -Q</userinput>
/home/me/project/prog1.o
/home/me/project/prog2.o
cc -o prog1.o -c prog1.c
cc -o prog2.o -c -DFOO prog2.c
cc -o prog1 prog1.o prog2.o
    </screen>

  </section>

  <section id="sect-get-launch-dir">
  <title>Finding the Invocation Directory:  the &GetLaunchDir; Function</title>

    <para>

    If you need to find the directory from
    which the user invoked the &scons; command,
    you can use the &GetLaunchDir; function:

    </para>

    <sconstruct>
env = Environment(
    LAUNCHDIR = GetLaunchDir(),
)
env.Command('directory_build_info',
            '$LAUNCHDIR/build_info'
            Copy('$TARGET', '$SOURCE'))
    </sconstruct>

    <para>

    Because &SCons; is usually invoked from the top-level
    directory in which the &SConstruct; file lives,
    the &Python; <function>os.getcwd()</function>
    is often equivalent.
    However, the &SCons;
    <literal>-u</literal>,
    <literal>-U</literal>
    and
    <literal>-D</literal>
    command-line options,
    when invoked from a subdirectory,
    will cause &SCons; to change to the directory
    in which the &SConstruct; file is found.
    When those options are used,
    &GetLaunchDir; will still return the path to the
    user's invoking subdirectory,
    allowing the &SConscript; configuration
    to still get at configuration (or other) files
    from the originating directory.

    </para>

  </section>

  <!-- Former unpublished chapter now included as a section here: -->
  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="sideeffect.xml"/>

  <section id="sect-virtualenv">
  <title>Using Python Virtual Environments</title>

    <para>

      &Python; supports lightweight "virtual environments" (usually
      abbreviated <firstterm>virtualenv</firstterm>) which allow
      encapsulation / isolation of package dependencies for a project.
      When a &Python; program is executed in the context of a
      virtualenv, the paths for package imports are amended so the
      modules in the virtualenv are preferred. Depending on how the
      virtualenv was configured, system paths may or may not be used
      as a fallback (the default is not).

    </para>

    <para>
      &SCons; itself works as expected when executed within a virtualenv.
      However, there may be issues if the project needs
      to build using external commands written in &Python;
      which are installed in the virtualenv,
      or calls the &Python; interpreter to run a script.
      &SCons; launches command actions using a special restricted
      <envar>PATH</envar> setting which the new process uses
      to find executables.
      This path is part of the execution environment
      (see  <xref linkend="sect-execution-environments"/>),
      and by default,
      does not contain any information about the virtualenv.
      The result can be commands not found,
      or scripts executed with the system default copy of &Python;
      rather than the virtualenv one, possibly causing incorrect imports.
      If you encounter this problem, &SCons; provides a mechanism
      to more fully integrate with a virtualenv.
    </para>

    <para>
      Use the <option>--enable-virtualenv</option>
      to import virtualenv-related environment variables to the
      execution environment (&cv-link-ENV;)
      and to modify the execution environment's <envar>PATH</envar>
      appropriately to prefer the virtualenv executables and
      &Python; interpreter.
    </para>

    <para>
      To make this setting permanent, you can either:
    </para>

    <itemizedlist>
      <listitem>
        <para>Add it to the <envar>SCONSFLAGS</envar> environment variable , or</para>
      </listitem>
      <listitem>
        <para>Set <envar>SCONS_ENABLE_VIRTUALENV=1</envar> in your environment.</para>
      </listitem>
    </itemizedlist>

    <para>
      <envar>SCONSFLAGS</envar> is the preferred approach,
      as it's easier to manage a single variable controlling &SCons;
      behavior than multiples.
      If enabled by environment variable,
      the special virtualenv behavior can be disabled for the current
      run using the <option>--ignore-virtualenv</option> option.
    </para>

    <para>
      You can query the state at runtime by calling
      the &f-link-Virtualenv; global function.
      It returns a path to the virtualenv's home directory,
      or an empty string if &SCons; is not running in a virtualenv.
    </para>

    <note><para>
      &f-Virtualenv; returns a path even if &SCons;
      is run from an unactivated virtualenv.
      A virtualenv does not have to be activated to be used,
      you only need to use the path to its &Python; interpreter,
      but only an activated virtualenv makes available the
      suitable <envar>PATH</envar> elements for &SCons; to
      copy in when <option>--enable-virtualenv</option> is used.
    </para></note>

  </section>

</chapter>
