from __future__ import absolute_import, division, print_function

import libtbx.load_env
from libtbx.str_utils import show_string
from libtbx.utils import \
  warn_if_unexpected_md5_hexdigest, \
  write_this_is_auto_generated
from libtbx.env_config import darwin_shlinkcom, get_boost_library_with_python_version
import glob
import os
import sys
from shutil import copystat
Import("env_base", "env_etc")
# Boost source exists, some Boost libraries will be built
env_etc.boost_dist = libtbx.env.dist_path("boost")
env_etc.boost_include = env_etc.boost_dist
build_boost_libs = True
env_etc.boost_python_lib = 'boost_python'
env_etc.boost_numpy_lib = 'boost_numpy'
# Boost source does not exist, headers and libraries exist
# boost directory in cctbx_project is picked up by find_in_repositories
if not os.path.isdir(env_etc.boost_dist) or 'cctbx_project' in env_etc.boost_dist \
  or env_etc.boost_dist == sys.prefix or libtbx.env.module_is_installed("boost_adaptbx"):
  # for conda, standard locations are already defined in libtbx/SConscript
  if libtbx.env.build_options.use_conda:
    env_etc.boost_dist = env_etc.conda_prefix
    env_etc.boost_include = env_etc.conda_cpppath[0]
    for p in env_etc.conda_cpppath:
      if os.path.isdir(os.path.join(p, 'boost')):
        env_etc.boost_include = p
        break
    for p in env_etc.conda_libpath:
      if p not in env_base['LIBPATH']:
        env_base.Prepend(LIBPATH=env_etc.conda_libpath)
  else:
    # this may not work for Windows, but conda should be used for Windows
    env_etc.boost_dist = sys.prefix
    env_etc.boost_include = os.path.join(env_etc.boost_dist, 'include')
    env_base.Prepend(LIBPATH=[os.path.join(sys.prefix, 'lib'),
                              os.path.join(sys.prefix, 'lib64')])
  build_boost_libs = False
  env_etc.boost_python_lib = get_boost_library_with_python_version("boost_python", env_base['LIBPATH'])
  env_etc.boost_numpy_lib = get_boost_library_with_python_version("boost_numpy", env_base['LIBPATH'])
env_etc.boost_adaptbx_dist = libtbx.env.dist_path("boost_adaptbx")
env_etc.boost_adaptbx_include = os.path.dirname(env_etc.boost_adaptbx_dist)

# We do not use SCons TryRun anymore to find Boost version
# as this is a hindrance for CCP4 people when they cross-compile Phaser
# (c.f. Marcin's email mid-october 2012)
env_etc.boost_version = libtbx.env.boost_version
print("BOOST_VERSION: %i" %env_etc.boost_version)

# If Boost.Thread was asked for at configuration time and if it is available
# we modify env_base to link the DLL's necessary for code using Boost.Thread
# to work. Since all other SCons environment derive from env_base, they
# are all ready to build Boost.Thread-dependent code if the build was
# configured to use Boost.Thread. This makes it much simpler for the
# developers and it is pretty much harmless, as linking dynamic libraries
# to other libraries or programs which do not use them should not create
# any issue.
if (libtbx.env.build_options.enable_boost_threads and
  env_etc.boost_thread_support):
  boost_thread_uses_winthreads = (sys.platform == 'win32')
  # MinGW compiler 5.3.0 appears to compile boost_threads
  # with the same condition as win32.
  # This environment is to be used latter to build Boost libraries
  # So we need a clean one without extra library linking.
  env_boost_shlibs_base = env_base.Clone()
  libs = ['boost_thread', 'boost_system']
  if boost_thread_uses_winthreads:
    libs.append('boost_chrono')
  # In case env_base specifies LIBS already, don't override but instead
  # append to them.
  env_base.Append(LIBS=libs)
  if not boost_thread_uses_winthreads:
    env_base.Append(CXXFLAGS='-pthread',
                    SHCXXFLAGS='-pthread',
                    LINKFLAGS='-pthread')

if (not env_etc.no_boost_python):
  env_etc.cxxflags_bpl_defines_base = [
    "-DBOOST_PYTHON_MAX_BASES=2"]
  if (libtbx.env.build_options.boost_python_no_py_signatures):
    env_etc.cxxflags_bpl_defines_base.append(
      "-DBOOST_PYTHON_NO_PY_SIGNATURES")
  # conda build of Boost does not have
  # boost::python::cxxabi_cxa_demangle_is_broken() for
  # boost_adaptbx/meta_ext.cpp
  if (libtbx.env.build_options.use_conda):
    env_etc.cxxflags_bpl_defines_base.append("-DUSE_CONDA")
  env_no_includes_boost_python_ext = env_base.Clone(
    SHLINKFLAGS=env_etc.shlinkflags_bpl,
    SHLIBPREFIX="")
  # Do not override the libraries linked by env_base
  env_no_includes_boost_python_ext.Append(
    LIBS=[env_etc.boost_python_lib] + env_etc.libs_python + env_etc.libm)
  env_no_includes_boost_python_ext.Append(
    LIBPATH=env_etc.libpath_python)
  env_no_includes_boost_python_ext.Append(
    SHCXXFLAGS=env_etc.cxxflags_bpl_defines_base,
    PCHPDBFLAGS=env_etc.cxxflags_bpl_defines_base)
  env_no_includes_boost_python_ext.Replace(
    SHLIBSUFFIX=env_etc.extension_module_suffix)
  if (env_etc.compiler == "win32_cl" and env_etc.have_manifest_tool):
    env_no_includes_boost_python_ext.Append(WINDOWS_EMBED_MANIFEST = True)
  elif (env_etc.mac_os_use_dsymutil):
    # Debugging with DWARF + dSYM works much more reliably with XCode 3 or 4 using gdb
    # Using lldb, the following tricks aren't necessary but lldb seems pretty flawed
    # when it comes to debug C++ code
    env = env_no_includes_boost_python_ext
    env['SHLINKCOM'] = [env['SHLINKCOM'], 'dsymutil $TARGET']
    env_base['LINKCOM'] = [env_base['LINKCOM'], 'dsymutil $TARGET']

  Export("env_no_includes_boost_python_ext")
  env_pure_boost_python_ext = env_no_includes_boost_python_ext.Clone()
  env_etc.include_registry.set_boost_dir_name(env_etc.boost_dist)
  env_etc.include_registry.append(
    env=env_pure_boost_python_ext,
    paths=[
      libtbx.env.under_build("include"),
      env_etc.boost_include,
      env_etc.python_include])

  # Argument-dependent lookup fails on GCC 4.1.2 (CentOS 5) and Boost >= 1.60
  # The "get" function fails to resolve in boost/math/tools/roots.hpp
  # Explicitly use "boost::fusion::get"
  if ( (env_etc.gcc_version == 40102) and (env_etc.boost_version >= 106000) ):
    directory = libtbx.env.module_dist_paths.get('boost','')
    filename = os.path.join(abs(directory), 'boost/math/tools/roots.hpp')
    old_filename = filename + '.original'
    if (os.path.exists(filename)):
      os.rename(filename, old_filename)
      with open(old_filename,'r') as f_old, open(filename, 'w') as f_new:
        lines = f_old.readlines()
        for line in lines:
          line = line.replace('= get<', '= boost::fusion::get<')
          f_new.write(line)
      copystat(old_filename, filename)
      os.remove(old_filename)

  conf = env_pure_boost_python_ext.Clone().Configure()
  if (not conf.TryCompile("#include <iostream>", extension=".cpp")):
    sys.exit("""
FATAL:

********************** C++ compiler does not work. **********************

See build/config.log for details""")
  conf.Finish()
  conf = env_pure_boost_python_ext.Clone().Configure()
  if (not conf.TryCompile("#include <Python.h>", extension=".cpp")):
    raise RuntimeError("""
FATAL:

******************** Python.h include not available. ********************

On some Linux machines you may need to install the "python-dev" package,
e.g. with "yum install python-dev" or "apt-get install python-dev".
""")
  conf.Finish()
  env_boost_python_ext = env_pure_boost_python_ext.Clone()
  env_etc.include_registry.prepend(
    env=env_boost_python_ext,
    paths=[env_etc.libtbx_include])
  Export("env_boost_python_ext")

  # Precompiled headers for the like of Boost Python, standard library, etc.
  if libtbx.env.build_options.precompile_headers:
    if sys.platform == 'win32':
      precompiled_cpp = '#' + os.path.join('boost_adaptbx', 'precompiled.cpp')
      precompiled_h = 'boost_adaptbx/precompiled.h'
      env_no_includes_boost_python_ext['PCH'], _ \
                = env_boost_python_ext.PCH(precompiled_cpp)
      env_no_includes_boost_python_ext['PCHSTOP'] = precompiled_h
      env_no_includes_boost_python_ext.Append(CPPFLAGS=[ '/FI' + precompiled_h ])
    elif env_etc.clang_version is not None:
      # 1st trick:
      # use Command to execute $SHCXXCOM with an extra option to produce
      # a precompiled header a la clang
      precompiled_h_gch = "#boost_adaptbx/precompiled.h.gch"
      env_pch = env_pure_boost_python_ext.Clone()
      env_pch.Append(SHCXXFLAGS=[ '-x', 'c++-header' ])
      env_pch.Command(target=precompiled_h_gch,
                      source="precompiled.h",
                      action="$SHCXXCOM")
      # 2nd trick
      # use an emitter so that the dependency on the precompiled header
      # is added on-the-fly to the target of SharedLibrary
      # (got the idea from http://scons.org/wiki/GchBuilder)
      explicit_include = [ '-include', 'boost_adaptbx/precompiled.h' ]
      def gcc_style_pch_emitter(target, source, env):
        import SCons.Defaults
        # emitters are not chained, so we need to call the default one
        # otherwise some setup is not done and scons will refuse to link
        # shared libraries from what it thinks are static objects
        SCons.Defaults.SharedObjectEmitter(target, source, env)
        env.Depends(target, precompiled_h_gch)
        return target, source
      for env in (env_pure_boost_python_ext, env_no_includes_boost_python_ext):
        env.Append(SHCXXFLAGS=explicit_include)
        for suffix in ('.cxx', '.cpp', '.cc'):
          env['BUILDERS']['SharedObject'].add_emitter(suffix,
                                                      gcc_style_pch_emitter)

  if not libtbx.env.module_is_installed("boost_adaptbx"):
    env_pure_boost_python_ext.SharedLibrary(
      target="#lib/boost_python_hybrid_times_ext",
      source="hybrid_times_ext.cpp")

    env_pure_boost_python_ext.SharedLibrary(
      target="#lib/boost_rational_ext",
      source="rational_ext.cpp")

    env = env_pure_boost_python_ext.Clone()
    env_etc.include_registry.prepend(
      env=env,
      paths=[env_etc.boost_adaptbx_include])
    env.SharedLibrary(
      target="#lib/boost_python_meta_ext",
      source="meta_ext.cpp")
    env.SharedLibrary(
      target="#lib/boost_optional_ext",
      source="optional_ext.cpp")
    env.SharedLibrary(
      target="#lib/std_pair_ext",
      source="std_pair_ext.cpp")
    env.SharedLibrary(
      target="#lib/boost_tuple_ext",
      source="tuple_ext.cpp")
    env.SharedLibrary(
      target="#lib/boost_adaptbx_python_streambuf_test_ext",
      source="tests/python_streambuf_test_ext.cpp")
    SConscript("graph/SConscript")

    env = env_base.Clone()
    env_etc.include_registry.prepend(
      env=env,
      paths=[env_etc.boost_adaptbx_include])
    env.Program(
      target="tests/tst_optional_copy",
      source="tests/tst_optional_copy.cpp")

  if build_boost_libs:
    # Build Boost.filesystem
    env_fs = env_base.Clone()
    env_etc.include_registry.prepend(env=env_fs, paths=[env_etc.boost_include])
    fs_root = '#' + os.path.join(
      os.path.basename(env_etc.boost_dist), 'libs', 'filesystem', 'src')
    fs_src_base = ['operations.cpp', 'path.cpp', 'path_traits.cpp', 'portability.cpp',
                   'unique_path.cpp', 'utf8_codecvt_facet.cpp',
                   'codecvt_error_category.cpp',
                  ]
    if env_etc.boost_version >= 107200:
      fs_src_base.extend(['directory.cpp', 'exception.cpp'])
      env_fs.Append(LIBS=['boost_system'])
    fs_src = [os.path.join(fs_root, path) for path in fs_src_base]
    if (sys.platform == 'win32'):     # Windows
      fs_src.append(os.path.join(fs_root, 'windows_file_codecvt.cpp'))
      #env_fs.Append(CPPDEFINES={'BOOST_FILESYSTEM_DYN_LINK':1})
      env_fs.Append(CPPDEFINES={'BOOST_FILESYSTEM_STATIC_LINK':1})
      env_fs.StaticLibrary(
        target='#lib/boost_filesystem',
        source=fs_src)
    else:                             # Linux, macOS
      env_fs.SharedLibrary(
        target='#lib/libboost_filesystem',
        source=fs_src)
    darwin_shlinkcom(env_etc, env_fs,
                    lo="boost/libs/filesystem/src/libboost_filesystem.lo",
                    dylib="lib/libboost_filesystem.dylib")

    # Build Boost.Thread if asked
    #
    # We may also need to build Boost.System if we use the so-called modular
    # Boost distribution. Then the Windows build requires Boost.Chrono (by
    # Windows, I mean everything but mingw).
    # Notes:
    # 1. There are two alternatives: pthreads or winthreads.
    #    We assume that mingw uses the former and that anything else on Windows
    #    uses winthreads.
    # 2. On Windows, we have to carefully set some macros so that the
    #    right __declspec(export) are issued so that the .lib associated with
    #    the .dll are produced. Those macros are marked "For Windows" below.
    # 3. On Windows, it is necessary to link boost_thread.dll with
    #    boost_chrono.lib and boost_system.lib, as well as boost_chrono.dll with
    #    boost_systeml.lib. Since it is irrelevant on MacOS and Linux, we
    #    don't use a conditional.
    if (libtbx.env.build_options.enable_boost_threads and
        env_etc.boost_thread_support):
      def simple_darwin_shlinkcom(env_etc, env, libname):
        darwin_shlinkcom(env_etc, env,
                        lo="boost/libs/%s/src/libboost_%s.lo" % ((libname,)*2),
                        dylib="lib/libboost_%s.dylib" % libname)

      env_boost_thread_and_co = env_boost_shlibs_base.Clone()

      # the following is necessary to avoid a bad __declspec on Windows
      # in Boost.Thread (don't ask!)
      env_boost_thread_and_co.Append(CPPDEFINES={'BOOST_SYSTEM_NO_DEPRECATED':1})
      env_etc.include_registry.append(
        env=env_boost_thread_and_co,
        paths=[env_etc.boost_include])

      # Boost.Thread
      env_boost_threads = env_boost_thread_and_co.Clone()
      simple_darwin_shlinkcom(env_etc, env_boost_threads, 'thread')
      boost_thread_dir = os.path.join('libs', 'thread', 'src')
      boost_thread_src = ['future.cpp']
      if boost_thread_uses_winthreads:
        boost_thread_src.extend(
          os.path.join('win32', p)
          for p in ('thread.cpp', 'tss_pe.cpp', 'tss_dll.cpp'))
      else:
        boost_thread_src.extend(
          os.path.join('pthread', p)
          for p in ('thread.cpp', 'once.cpp',))
      boost_thread_src = [
        '#' + os.path.join(
          os.path.basename(env_etc.boost_dist), boost_thread_dir, p)
        for p in boost_thread_src]
      # For Windows (c.f. point 2 above)
      env_boost_threads.Append(CPPDEFINES={'BOOST_THREAD_BUILD_DLL':1})
      libs = ['boost_system']
      if sys.platform.startswith("linux"): # require realtime extensions for clock_gettime
        libs.append('rt')
      if boost_thread_uses_winthreads:
        libs.append('boost_chrono')
        env_boost_threads.StaticLibrary(
          target='#lib/boost_thread',
          source=boost_thread_src,
          LIBS=libs)
      else:
        env_boost_threads.SharedLibrary(
          target='#lib/boost_thread',
          source=boost_thread_src,
          LIBS=libs)

      # Boost.System?
      if os.path.isdir(os.path.join(env_etc.boost_dist, 'boost', 'system')):
        env_boost_system = env_boost_thread_and_co.Clone()
        simple_darwin_shlinkcom(env_etc, env_boost_system, 'system')
        boost_system_src = [
          '#' + os.path.relpath(p, os.path.dirname(env_etc.boost_dist))
          for p in glob.glob(
            os.path.join(env_etc.boost_dist, 'libs', 'system', 'src', '*.cpp'))]
        # For Windows (c.f. point 2 above)
        env_boost_system.Append(
          CPPDEFINES={'BOOST_SYSTEM_DYN_LINK':1})
        if boost_thread_uses_winthreads:
          env_boost_system.StaticLibrary(
            target='#lib/boost_system',
            source=boost_system_src)
        else:
          env_boost_system.SharedLibrary(
            target='#lib/boost_system',
            source=boost_system_src)

      #Boost.Chrono?
      if boost_thread_uses_winthreads:
        env_boost_chrono = env_boost_thread_and_co.Clone()
        simple_darwin_shlinkcom(env_etc, env, 'chrono')
        boost_chrono_src = [
          '#' + os.path.relpath(p, os.path.dirname(env_etc.boost_dist))
          for p in glob.glob(
            os.path.join(env_etc.boost_dist, 'libs', 'chrono', 'src', '*.cpp'))]
        # For Windows (c.f. point 2 above)
        env_boost_chrono.Append(CPPDEFINES={'BOOST_CHRONO_DYN_LINK':1})
        env_boost_chrono.StaticLibrary(
          target='#lib/boost_chrono',
          source=boost_chrono_src,
          LIBS=['boost_system'])

  # Build a Python extension using Boost.Thread so as to test it works.
  # This also constitutes a nice real-life example of
  # how to build such extensions.
  if (libtbx.env.build_options.enable_boost_threads and
      env_etc.boost_thread_support and not libtbx.env.module_is_installed("boost_adaptbx")):
    env_boost_threads_test = env_pure_boost_python_ext.Clone()
    env_etc.include_registry.append(
      env=env_boost_threads_test,
      paths=[env_etc.boost_adaptbx_include])
    if boost_thread_uses_winthreads:
      env_boost_threads_test.StaticLibrary(
        target="#lib/boost_adaptbx_boost_thread_test_ext",
        source="tests/boost_thread_test.cpp")
    else:
      env_boost_threads_test.SharedLibrary(
        target="#lib/boost_adaptbx_boost_thread_test_ext",
        source="tests/boost_thread_test.cpp")

  if build_boost_libs:
    env = env_base.Clone(LIBS=env_etc.libs_python)
    env.Append(LIBPATH=env_etc.libpath_python)
    env.Append(SHCXXFLAGS=env_etc.cxxflags_bpl_defines_base)
    if (libtbx.env.build_options.boost_python_bool_int_strict):
      env.Append(SHCXXFLAGS=["-DBOOST_PYTHON_BOOL_INT_STRICT"])
    env.Replace(SHLINKFLAGS=env_etc.shlinkflags)
    env.Append(CXXFLAGS=env_etc.cxxflags_bpl_defines_base)
    env.Replace(LINKFLAGS=env_etc.shlinkflags)
    env_etc.include_registry.append(
      env=env,
      paths=[env_etc.boost_include, env_etc.python_include])

    # BOOST_PYTHON specific configuration
    env_bpy = env.Clone()
    env_bpy.Append(SHCXXFLAGS="-DBOOST_PYTHON_SOURCE")
    env_bpy.Append(CXXFLAGS="-DBOOST_PYTHON_SOURCE")

    darwin_shlinkcom(env_etc, env_bpy,
                    lo="boost/libs/python/src/libboost_python.lo",
                    dylib="lib/libboost_python.dylib")

    # fixed list of file names introduced 2009-09-29, due to major changes in
    # boost/libs/python/build/Jamfile.v2 svn rev. 56305 (new Python 3 support)
    bpl_dll_sources = """\
numeric.cpp
list.cpp
long.cpp
dict.cpp
tuple.cpp
str.cpp
slice.cpp
converter/from_python.cpp
converter/registry.cpp
converter/type_id.cpp
object/enum.cpp
object/class.cpp
object/function.cpp
object/inheritance.cpp
object/life_support.cpp
object/pickle_support.cpp
errors.cpp
module.cpp
converter/builtin_converters.cpp
converter/arg_to_python_base.cpp
object/iterator.cpp
object/stl_iterator.cpp
object_protocol.cpp
object_operators.cpp
wrapper.cpp
import.cpp
exec.cpp
object/function_doc_signature.cpp
""".splitlines()

    # Boost 1.65 removed the numeric module
    if env_etc.boost_version >= 106500:
      bpl_dll_sources.remove("numeric.cpp")

    prefix = "#"+os.path.join(
      os.path.basename(env_etc.boost_dist), "libs", "python", "src")
    bpl_dll_sources = [os.path.join(prefix, path) for path in bpl_dll_sources]
    #
    env_bpy.Repository(os.path.dirname(env_etc.boost_dist))
    if (env_etc.static_bpl):
      env_bpy.Append(SHCXXFLAGS=["-DBOOST_PYTHON_STATIC_LIB"])
      env_bpy.StaticLibrary(target="#lib/boost_python", source=bpl_dll_sources)
    else:
      env_bpy.SharedLibrary(target="#lib/boost_python", source=bpl_dll_sources)

    # Boost 1.63 added the numpy submodule. Let's build this now.
    if env_etc.boost_version >= 106300:
      try:
        import numpy
        env_npy = env.Clone()

        # Since we have numpy, we can ask it for it's header path
        env_etc.include_registry.append(env=env_npy, paths=[numpy.get_include()])

        # Set up the custom OSX linking command, like everything else in this module
        darwin_shlinkcom(env_etc, env_npy,
                  lo="boost/libs/python/src/libboost_numpy.lo",
                  dylib="lib/libboost_numpy.dylib")

        env_npy.Append(SHCXXFLAGS=["-DBOOST_NUMPY_SOURCE"])
        env_npy.Append(LIBS="boost_python")

        bpy_numpy_sources = ["numpy/dtype.cpp", "numpy/matrix.cpp",
                            "numpy/ndarray.cpp", "numpy/numpy.cpp",
                            "numpy/scalars.cpp", "numpy/ufunc.cpp"]

        # Put the boost repository prefix on these
        bpy_numpy_sources = [os.path.join(prefix, path) for path in bpy_numpy_sources]

        if env_etc.static_bpl or \
          (env_etc.boost_version == 106300 and sys.platform == 'win32'):
          env_npy.Append(SHCXXFLAGS=["-DBOOST_NUMPY_STATIC_LIB"])
          builder = env_npy.StaticLibrary
        else:
          builder = env_npy.SharedLibrary
        builder(target="#lib/boost_numpy", source=bpy_numpy_sources)

      except ImportError:
        # No numpy, no boost_numpy module
        pass

    if (bool(int(ARGUMENTS.get("boost_python_tests", "0")))):
      warn_if_unexpected_md5_hexdigest(
        path=libtbx.env.under_dist("boost", "libs/python/test/Jamfile.v2"),
        expected_md5_hexdigests=[
          "c7a3dd81bf730635146f5c908ac982eb", # svn revision 39065M
          "d40aac0029bcd28f6e205ae3b30a1284", # svn revision 40216
          "b79f016d3ec10cf1625a9e006e605428", # svn revision 40714
          "f948983d970fd47e83a3a785bb54520a", # svn revision 41550
          "c3d84006331d534840c42ab956fdfa05", # svn revision 50367M
          "cedfd061d767a27413ef4a75ee0e446f", # svn revision 56310
          "e5507482a1937825e4c9f3ffe555fc59", # svn revision 59331
          "351b57c6a60e484925ca0cb9ed0f8ade", # svn revision 61086
          "4fb583110f7424c341a0dd44ff3b3a2d", # svn revision 69551
          "fdde4c7231c301c3150b0a5605045914", # svn revision 72175
        ])
      bpl_tests = Split("""
  staticmethod
  shared_ptr
  enable_shared_from_this
  andreas_beyer
  polymorphism
  polymorphism2
  auto_ptr
  minimal
  args
  numpy
  enum
  exception_translator
  test_pointer_adoption
  operators
  callbacks
  defaults
  object
  list
  long
  dict
  tuple
  str
  virtual_functions
  back_reference
  implicit
  data_members
  ben_scott1
  bienstman1
  bienstman2
  bienstman3
  multi_arg_constructor
  extract
  opaque
  pickle1
  pickle2
  pickle3
  pickle4
  nested
  docstring
  pytype_function
  bienstman4
  bienstman5
  test_builtin_converters,builtin_converters_ext,test_builtin_converters
  cltree,cltree,test_cltree
  m1,m1,newtest
  m2,m2,newtest
  iterator,iterator_ext,iterator
  input_iterator,input_iterator,iterator
  crossmod_exception_a,crossmod_exception_a,crossmod_exception
  crossmod_exception_b,crossmod_exception_b,crossmod_exception
  vector_indexing_suite
  return_arg
  keywords,keywords,keywords_test
  properties
  map_indexing_suite;int_map_indexing_suite;a_map_indexing_suite,map_indexing_suite_ext,map_indexing_suite
  injected
  slice
  const_argument
  raw_ctor
  pointer_vector
  wrapper_held_type
  polymorphism2_auto_ptr
  stl_iterator
  voidptr
  crossmod_opaque_a,crossmod_opaque_a,crossmod_opaque
  crossmod_opaque_b,crossmod_opaque_b,crossmod_opaque
  class
  """)
  # non-portable tests are not included above:
  #   calling_conventions
  #   calling_conventions_mf
  #
      Import("env_boost_python_ext")
      test_dir = libtbx.env.under_dist("boost", "libs/python/test")
      prefix = "#"+os.path.join(
        os.path.basename(env_etc.boost_dist), "libs", "python", "test")
      all_tst = []
      for bpl_test in bpl_tests:
        flds = bpl_test.split(",")
        assert len(flds) in (1,3)
        if (len(flds) == 1):
          src = bpl_test
          pyd = bpl_test + "_ext"
          tst = bpl_test
        else:
          src = flds[0]
          pyd = flds[1]
          tst = flds[2]
        env = env_pure_boost_python_ext.Clone()
        env.Repository(os.path.dirname(env_etc.boost_dist))
        ok = True
        source = []
        for s in src.split(";"):
          cpp = "%s.cpp" % s
          file_name = os.path.join(test_dir, cpp)
          if (not os.path.isfile(file_name)):
            print("Warning: source file not available:", show_string(file_name))
            ok = False
          else:
            source.append(os.path.join(prefix, cpp))
        if (ok):
          if (not tst in all_tst):
            all_tst.append(tst)
          env.SharedLibrary(target="#lib/%s" % pyd, source=source)
      all_tst = [os.path.join(test_dir, tst+".py") for tst in all_tst]
      #
      if sys.platform.startswith("linux"):
        base_lib = libtbx.env.under_build(path="base/lib")
        if (os.path.isdir(base_lib)):
          env_prog = env.Clone()
          env_prog.Append(LIBPATH=[base_lib])
          env_prog.Append(LIBS=[
            "-lpython%d.%d" % sys.version_info[:2],
            "-lpthread",
            "-lutil",
            "-ldl"])
          exe = env_prog.Program(
            target="boost/libs/python/test/exec_dynamic",
            source=[os.path.join(prefix, "exec.cpp")])
          libtbx.env.write_dispatcher_in_bin(
            source_file=exe[0].get_abspath(),
            target_file="boost_libs_python_test_exec_dynamic")
          all_tst.append("$ boost_libs_python_test_exec_dynamic %s"
            % show_string(os.path.join(test_dir, "exec.py")))
      #
      if (os.name != "nt"):
        env.SharedLibrary(
          target="#lib/boost_adaptbx_char_array_ext",
          source=["char_array_ext.cpp"])
        all_tst.append(
          libtbx.env.under_dist("boost_adaptbx", "tests/tst_char_array.py"))
      #
      if (os.name == "nt"):
        boost_python_run_tests = \
          "%s\\boost_python_run_tests.bat" % libtbx.env.build_path
        f = open(boost_python_run_tests, "w")
        for tst in all_tst:
          if (tst.startswith("$ ")):
            print('call %s' % tst[2:], file=f)
          else:
            print('call libtbx.python %s' % show_string(tst), file=f)
        f.close()
      else:
        boost_python_run_tests = \
          "%s/boost_python_run_tests.csh" % abs(libtbx.env.build_path)
        f = open(boost_python_run_tests, "w")
        print("#! /bin/csh -f", file=f)
        print("set verbose", file=f)
        for tst in all_tst:
          if (tst.startswith("$ ")):
            print(tst[2:], file=f)
          else:
            print('libtbx.python "%s"' % show_string(tst), file=f)
        f.close()
        os.chmod(boost_python_run_tests, 0o755)

  def write_type_id_eq_h():
    unsigned_types = [
      "unsigned short",
      "unsigned",
      "unsigned long",
      "unsigned long long"]
    test_code = """\
#include <typeinfo>
#include <iostream>
#include <stddef.h>

int
main()
{
  std::cout
%s
    << std::endl;
  return 0;
}
""" % "\n".join(["    << static_cast<int>(typeid(%s) == typeid(size_t))" % u
      for u in unsigned_types])
    env = env_base.Clone()
    # temp fix for Xcode 15 and current static conda environments
    if sys.platform.startswith('darwin') \
      and libtbx.env.build_options.use_conda \
      and 'conda' not in libtbx.env.build_options.compiler \
      and env_etc.clang_version[0] > 14:
      env.Replace(
        LIBPATH=[env_base['LIBPATH'][0]],
        LIBS=[])  # no need to link Boost libraries here
    else:
      env.Replace(LIBS=[])  # no need to link Boost libraries here
    conf = env.Configure()
    flag, output = conf.TryRun(test_code, extension='.cpp')
    conf.Finish()
    if (not flag):
      see_message_above = \
        'Failure building small C++ program for obtaining unsigned sizes.' \
        ' Please inspect "config.log" for details'
      raise RuntimeError(see_message_above)
    output = output.rstrip()
    if (   len(output) != len(unsigned_types)
        or len(output.replace("0", "").replace("1", "")) != 0
        or output.count("1") != 1):
      raise RuntimeError('Unexpected output: "%s"' % output)
    dir_name = libtbx.env.under_build("include/boost_adaptbx")
    if (not os.path.isdir(dir_name)):
      os.makedirs(dir_name)
    assert os.path.isdir(dir_name)
    if (1): # XXX backward compatibility 2009-10-14
      file_name = os.path.join(dir_name, "boost_python_type_id_eq.h")
      if (os.path.isfile(file_name)):
        try: os.remove(file_name)
        except OSError: pass
    file_name = os.path.join(dir_name, "type_id_eq.h")
    with open(file_name, "w") as f:
      write_this_is_auto_generated(
        f=f,
        file_name_generator="cctbx_project/boost_adaptbx/SConscript")
      guard = "BOOST_ADAPTBX_TYPE_ID_EQ_H"
      print("#ifndef", guard, file=f)
      print("#define", guard, file=f)
      print(file=f)
      for u,c in zip(unsigned_types, output):
        if (c == "1"):
          print("#define BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_" \
            + u.replace(" ", "_").upper(), file=f)
      print(file=f)
      print("#endif // GUARD", file=f)
  write_type_id_eq_h()

  if (os.path.isfile(libtbx.env.under_build("lib/libswig_class_example.a"))):
    # this example requires these manual steps:
    #   gunzip -c swig-1.3.24.tar.gz | tar xf -
    #   cd SWIG-1.3.24
    #   ./configure
    #   make
    #   cd Examples/python/class
    #   make
    #   cp _example.so example.py $LIBTBX_BUILD/lib
    #   ar r $LIBTBX_BUILD/lib/libswig_class_example.a example.o
    env = env_pure_boost_python_ext.Clone()
    env.Prepend(LIBS=["swig_class_example"])
    env.SharedLibrary(
      target="#lib/boost_python_swig_args_ext",
      source="swig_args_ext.cpp")
