from __future__ import absolute_import, division, print_function

import os
import platform
import re
import shutil
import subprocess
import sys

import SCons

import libtbx.load_env
import libtbx.env_config
from libtbx import easy_run
from libtbx.utils import getenv_bool, Sorry
from libtbx.str_utils import show_string
from libtbx.path import norm_join, full_command_path

class empty(object):

  def __init__(self):
    open(libtbx.env.under_build("include_paths"), "w")

  def __setattr__(self, key, value):
    self.__dict__.__setitem__(key, value)
    if (key.endswith("_common_includes")):
      include_paths = libtbx.env_config.unique_paths(paths=value)
      for path in include_paths:
        if (path.find(os.pathsep) >= 0):
          raise RuntimeError(
            'include path with embedded "%s" character: %s' % (
              os.pathsep, show_string(path)))
      print(key, os.pathsep.join(include_paths), file=open(libtbx.env.under_build("include_paths"), "a"))

  def _():
    def fget(self):
      return self.__dict__.setdefault("_cflags_base", [])
    def fset(self, value):
      self._cflags_base = value
    return locals()
  cflags_base = property(**_())

  def enable_fast_linalg(self, env):
    """ Provide a null default for the method added on env_etc
        in fast_linalg/SConscript iff module fast_linalg is configured.
    """
    pass


def set_python_include_and_libs(env_etc):
  env_etc.python_include = libtbx.env_config.python_include_path()
  if (sys.platform == "win32"):
    env_etc.libs_python = ["python" + str(sys.version_info.major) + str(sys.version_info.minor)]
    env_etc.libpath_python = [sys.prefix + r"\libs"]
  else:
    env_etc.libs_python = []
    env_etc.libpath_python = []
  if (env_etc.compiler.startswith("darwin_")):
    env_etc.python_framework = "/".join(
      env_etc.python_include.split("/")[:-2] + ["Python"])

def determine_pointer_size(env_base, env_etc): # currently unused
  test_code = """\
#include <iostream>
int main()
{
  std::cout << sizeof(void*) << std::endl;
  return 0;
}
"""
  env = env_base.Clone(
    CFLAGS=env_etc.cflags_base,
    CCFLAGS=env_etc.ccflags_base,
    CXXFLAGS=env_etc.cxxflags_base)
  conf = env.Configure()
  flag, output = conf.TryRun(test_code, extension='.cpp')
  conf.Finish()
  if (not flag): return None
  return int(output)

# XXX feature duplication, see env_etc.build_options.enable_cxx11
def determine_cpp0x_flag():
  result = getenv_bool(variable_name="LIBTBX_CPP0X", default=None)
  if (result is None):
    return False
  # mrt: disabling, because it is very conspicuous, and will be turned on
  # even when I do not want it
  #  result = libtbx.env.under_dist(
  #    module_name="boost",
  #    default=None,
  #    path=".svn",
  #    test=os.path.isdir)
  return result

env_etc = empty()
env_etc.no_boost_python = ARGUMENTS.get("no_boost_python")
if (env_etc.no_boost_python is not None):
  env_etc.no_boost_python = bool(int(env_etc.no_boost_python))
else:
  env_etc.no_boost_python = \
    not libtbx.env.build_options.build_boost_python_extensions
env_etc.norm_join = norm_join
env_etc.gcc_version = None
env_etc.icc_version = None
env_etc.clang_version = None
env_etc.msvc_version = None
env_etc.gcc_is_llvm_gcc = None
env_etc.apple_gcc_builds = libtbx.group_args(gcc=None, llvm=None)
env_etc.cxx11_is_available = None
env_etc.boost_thread_support = True # Let's be optimistic!

env_etc.include_registry = libtbx.env_config.include_registry() \
  .scan_boost(flag=libtbx.env.build_options.scan_boost)

# XXX backward compatibility 2007-11-18
def disable_strict_aliasing(env): pass
env_etc.disable_strict_aliasing = disable_strict_aliasing

def patch_scons_env_for_ad_hoc_debug(env, O123_replacement=["-O0", "-g"]):
  assert not env_etc.compiler.startswith("win")
  for kw in ["CCFLAGS", "SHCCFLAGS"]:
    dbg_ccflags = []
    for f in env[kw]:
      if   (f in ["-O1", "-O2", "-O3"]):
        dbg_ccflags.extend(O123_replacement)
      elif (f not in ["-ffast-math", "-funroll-loops"]):
        dbg_ccflags.append(f)
    env.Replace(**{kw: dbg_ccflags})
env_etc.patch_scons_env_for_ad_hoc_debug = patch_scons_env_for_ad_hoc_debug

def patch_scons_env(env, key, remove=set(), replace={}):
  remaining = []
  have_changes = False
  for v in env[key]:
    if (v in remove):
      have_changes = True
    elif (v in replace):
      remaining.append(replace[v])
      have_changes = True
    else:
      remaining.append(v)
  if (have_changes):
    env.Replace(**{key: remaining})
env_etc.patch_scons_env = patch_scons_env

compiler = libtbx.env.build_options.compiler
if (sys.platform == "win32"):
  if (compiler == "default"):
    env_etc.compiler = "win32_cl"
    cl_exe = full_command_path("cl.exe")
    mingw_exe = full_command_path("gcc.exe")
    if (cl_exe is None) and mingw_exe:
      # hack to detect mingw compiler on windows
      if "mingw" in mingw_exe:
        env_etc.compiler = "win32_mingw"
        libtbx.env.build_options.static_exe = True
  else:
    env_etc.compiler = "win32_"+compiler
    compiler = None
elif (sys.platform.startswith("darwin")):
  if (compiler == "default"):
    env_etc.compiler = "darwin_c++"
  else:
    env_etc.compiler = "darwin_"+compiler
    compiler = None
elif (os.name == "posix"):
  if (compiler == "default"):
    env_etc.compiler = "unix_gcc"
  else:
    env_etc.compiler = "unix_"+compiler
    compiler = None
if (compiler not in ("default", None)):
  sys.tracebacklimit = 0
  raise RuntimeError("Compiler not supported on this platform: %s" % compiler)
supported_compilers = (
  "unix_conda",
  "darwin_conda",
  "win32_cl",
  "win32_icc",
  "win32_mingw",
  "unix_mingw", # cross-compiling works only with
                # --build-boost-python-extensions=False
  "unix_gcc",
  "unix_gcc4",
  "unix_clang",
  "unix_icc",
  "unix_icpc",
  "darwin_c++",
  "darwin_gcc",
  "darwin_gcc-4.2", # tested with the "GCC 4.2 Developer Preview 1"
                    # distributed by Apple
                    # and the GCC 4.2 shipping with XCode 3.1
  "darwin_clang", # successfull compilation of phenix + smtbx
                  # with LLVM trunk revision 111316 and all tests pass.
                  # It will hopefully be so with the soon-to-be-released
                  # LLVM 2.8 which will most likely ship with the first
                  # official release of XCode 4 (currently in Preview).
)
if (not env_etc.compiler in supported_compilers):
  sys.tracebacklimit = 0
  raise RuntimeError("Unknown platform/compiler: %s. Choices are: %s" % (
    env_etc.compiler, ", ".join(supported_compilers)))
if (env_etc.compiler == "unix_icpc"):
  # backward compatibility for CCP4
  env_etc.compiler = "unix_icc"

static_exe = libtbx.env.build_options.static_exe

def gcc_common_warn_options():
  result = [
    "-Wall",
    "-Wno-sign-compare",
    "-Wno-unknown-pragmas",
    "-Wno-parentheses"
    ]
  if (    env_etc.gcc_version is not None
      and env_etc.gcc_version >= 40000):
    result.append("-Winit-self")
    if (    env_etc.gcc_version >= 40300
        and env_etc.gcc_version < 40501):
      # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43949
      result.append("-Wno-array-bounds")

    # compiler for CentOS 5 does not support -Wno-unused-local-typedefs
    # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=33255
    # flag supported only on gcc releases after September 2011
    if ( (env_etc.gcc_version == 40407) or (env_etc.gcc_version == 40504) or
         (env_etc.gcc_version >= 40602) ):
      result.append("-Wno-unused-local-typedefs")
      result.append("-Werror=vla")  # do not allow non-portable extension for dynamic array allocation
  return result

def clang_common_warn_options():
  return [ '-Wmost', '-Wno-unknown-pragmas', '-Wno-logical-op-parentheses',
           '-Wno-unused-local-typedefs' ]

def enable_more_warnings(env):
  """ Enable more warning for debug_level == 0 """
  if (env_etc._enable_more_warnings == "gcc"):
    # Here we rely on the fact that at debug_level == 0 implies the -w flag
    for flags in ["CCFLAGS", "SHCCFLAGS", "CXXFLAGS", "SHCXXFLAGS"]:
      have_change = False
      patched_flags = []
      for item in env[flags]:
        if (item == "-w"):
          patched_flags.extend(gcc_common_warn_options())
          have_change = True
        else:
          patched_flags.append(item)
      if (have_change):
        env.Replace(**{flags: patched_flags})
  elif env_etc._enable_more_warnings == "clang":
    # For clang, debug_level == 0 has no special flag
    # So search for -W flags instead
    for flags in ["CCFLAGS", "SHCCFLAGS", "CXXFLAGS", "SHCXXFLAGS"]:
      has_warnings_already = False
      for item in env[flags]:
        if item.startswith('-W'):
          has_warnings_already = True
          break
      if not has_warnings_already:
        env.Append(**{flags: clang_common_warn_options()})

def setup_clang_warning(env_etc, warning_level):
  env_etc.__dict__.setdefault("cflags_base", [])

  if warning_level == 0:
    # - pure C code is only 3rd party library we don't care cleaning up bad
    #   programming style: hence no warning
    #
    # - for our C++ code, Boost recent versions started to use C++0x features
    #   that clang warns about, so disable that. Disable array bound checking
    #   as Boost is again too tricky for clang to reason on
    env_etc.cflags_base.extend([ '-w' ])
    env_etc.ccflags_base.extend([ '-Wno-c++0x-extensions',
                                  '-Wno-array-bounds' ])
    env_etc._enable_more_warnings = "clang"
  elif warning_level == 1:
    # not sure about whether -Wmost is the best choice yet
    # time will tell
    env_etc.ccflags_base.extend([ '-Wmost' ])
  else:
    # idem
    env_etc.ccflags_base.extend([ '-Wmost', '-Werror' ])


env_etc.enable_more_warnings = enable_more_warnings
env_etc._enable_more_warnings = None

is_64bit_architecture = libtbx.env_config.is_64bit_architecture()

if (sys.platform == "win32" or
      env_etc.compiler.endswith("mingw")):  # cross-compiling for Windows
  if is_64bit_architecture:
    target_arch = 'x86_64'
  else:
    target_arch = 'x86'
  env_etc.static_libraries = 1
  env_etc.static_bpl = 0
  set_python_include_and_libs(env_etc)
  env_etc.extension_module_suffix = ".pyd"
  if (env_etc.compiler == "win32_cl"):
    if hasattr(sys, 'gettotalrefcount'): # only the debug build of Python has this function
      env_etc.extension_module_suffix = "_d.pyd" # then rename extension modules accordingly
                                                 # so that debug version of Python can load them
    cl_exe = full_command_path("cl.exe")
    if (cl_exe is None):
      raise RuntimeError("cl.exe not on PATH")
    VS_UNICODE_OUTPUT = os.environ.get('VS_UNICODE_OUTPUT')
    if VS_UNICODE_OUTPUT is not None:
      # Stop output from being redirected to IDE
      del os.environ['VS_UNICODE_OUTPUT']
    cl_info = easy_run.fully_buffered(
      command='"%s"' % cl_exe,
      join_stdout_stderr=True,
      stdout_splitlines=False).stdout_buffer
    if VS_UNICODE_OUTPUT is not None:
      # Reset VS_UNICODE_OUTPUT
      os.environ['VS_UNICODE_OUTPUT'] = VS_UNICODE_OUTPUT
    cl_ver_pat = re.compile(r'[Vv]ersion \W+ (\d+\.\d+)', re.X)
    m = cl_ver_pat.search(cl_info)
    visual_studio_version = m.group(1) if m is not None else None
    if visual_studio_version == "13.10":
      env_base = Environment(ENV=os.environ, MSVC_VERSION="7.1")
      env_etc.have_manifest_tool = False
      env_etc.msvc_version = 7
    elif visual_studio_version == "14.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="8.0", TARGET_ARCH=target_arch)
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 8
    elif visual_studio_version == "15.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="9.0", TARGET_ARCH=target_arch)
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 9
    elif visual_studio_version == "16.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="10.0", TARGET_ARCH=target_arch)
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 10
    elif visual_studio_version == "17.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="11.0", TARGET_ARCH=target_arch)
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 11
    elif visual_studio_version == "18.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="12.0", TARGET_ARCH=target_arch)
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 12
    elif visual_studio_version == "19.00":
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="14.0", TARGET_ARCH=target_arch, tools=['msvc','mslink', 'mslib'])
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 14
    elif visual_studio_version.startswith("19.1"):
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="14.1", TARGET_ARCH=target_arch, tools=['msvc','mslink', 'mslib'])
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 15
    elif visual_studio_version.startswith("19.2"):
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="14.2", TARGET_ARCH=target_arch, tools=['msvc','mslink', 'mslib'])
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 16
    elif visual_studio_version.startswith("19.3"):
      env_base = Environment(
        ENV=os.environ, MSVC_VERSION="14.3", TARGET_ARCH=target_arch, tools=['msvc','mslink', 'mslib'])
      env_etc.have_manifest_tool = True
      env_etc.msvc_version = 17
    else:
      raise RuntimeError("""\
Unknown or unsupported cl.exe version.

Minimum version is 7.1 (Visual Studio 2003).
For newer versions, please adjust this SConscript.
""")
    print("MSVC_VERSION:", env_base["MSVC_VERSION"])
    env_base.Replace(
      SHCC="cl",
      SHCXX="cl",
      SHLINK="link",
    )
    env_etc.c_link = "link"
    env_etc.ccflags_base = [
      "/nologo",
      "/D_SECURE_SCL=0",
      "/D_CRT_SECURE_NO_DEPRECATE",
      "/DNOMINMAX",
      "/DWIN32",
      "/DDLL_EXPORTS",
      "/D_WIN32_WINNT=_WIN32_WINNT_WIN7",
      "/bigobj", # for heavy use of template libraries
      "/wd4996", # warning C4996: ... was declared deprecated
      "/wd4068", # warning C4068: unknown pragma
      "/Z7", # produce program database file containing debugging symbols.
             # Harmless for release builds
      "/Zm800"]
    # include header files from cctbx_project/msvc9.0_include for VS 2008
    if (env_base['MSVC_VERSION'] == '9.0'):
      env_etc.ccflags_base.append(
        "/I%s" % os.path.join(libtbx.env.under_dist('libtbx', '..'),
                              'msvc9.0_include'))

    if (libtbx.env.build_options.debug_symbols):
      if (not env_etc.no_boost_python):
        print("""\
libtbx.scons: Warning: compiling with /MDd:
                       Boost.Python extensions will work reliably
                       only with a debug build of Python.""")
      env_etc.ccflags_base.extend([
        "/MDd",
        "/D_DEBUG",
        "/Z7", # see above
        "/D_HAS_ITERATOR_DEBUGGING=0"])
          # speed up memory allocation and deallocation in debug mode
    else:
      env_etc.ccflags_base.append("/MD")
    env_etc.cxxflags_base = [
      "/GR",
      "/EHsc"]
    if (libtbx.env.build_options.optimization):
      opts = ["/DNDEBUG", "/O2"]
      if (    libtbx.env.build_options.msvc_arch_flag is not None
          and not is_64bit_architecture):
        opts.append("/arch:%s" %libtbx.env.build_options.msvc_arch_flag)
      # stick with /fp:precise for VS2017 and later
      if env_etc.msvc_version > 7 and env_etc.msvc_version < 15:
        opts.append("/fp:fast")
    else:
      opts = ["/Od"]
    opts.append("/DBOOST_ALL_NO_LIB")
    env_etc.ccflags_base.extend(opts)
    env_etc.shlinkflags = ["/nologo", "/incremental:no", "/dll", "/MANIFEST"]
    if hasattr(sys, 'gettotalrefcount'): # are we compiling with debug version of Python?
      env_etc.ccflags_base.extend(["/DBOOST_DEBUG_PYTHON"]) # then BoostPython must be compiled in that mode

    if (libtbx.env.build_options.debug_symbols):
      env_etc.shlinkflags.append("/DEBUG")
      env_base.Prepend(LINKFLAGS=["/DEBUG"]) # write program database files with debug symbols
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
    env_etc.libm = []
    # handle std::snprintf for some dependencies that may need this macro
    # https://github.com/python/cpython/issues/80201
    if sys.version_info.major == 3 and sys.version_info.minor <= 8:
      env_etc.ccflags_base.append('/DHAVE_SNPRINTF')
      env_etc.cxxflags_base.append('/DHAVE_SNPRINTF')
    # needed for latest versions of Visual Studio 2022 so that type
    # limits are available
    if env_etc.msvc_version == 17:
      env_etc.ccflags_base.append('/D__STDC_LIMIT_MACROS')
      env_etc.cxxflags_base.append('/D__STDC_LIMIT_MACROS')
  elif (env_etc.compiler == "win32_icc"):
    cl_exe = full_command_path("icl.exe")
    if (cl_exe is None):
      raise RuntimeError("cl.exe not on PATH")
    cl_info = easy_run.fully_buffered(
      command='"%s"' % cl_exe,
      join_stdout_stderr=True,
      stdout_splitlines=False).stdout_buffer
    if (cl_info.find("Version 13.10") >= 0):
      env_base = Environment(ENV=os.environ, MSVC_VERSION="7.1")
    elif (cl_info.find("Version 14.00") >= 0):
      env_base = Environment(ENV=os.environ, MSVC_VERSION="8.0")
    else:
      env_base = Environment(ENV=os.environ)
    print("MSVC_VERSION:", env_base["MSVC_VERSION"])
    env_etc.have_manifest_tool = os.path.isfile(
      os.path.join(os.path.dirname(cl_exe), "mt.exe"))
    env_base.Replace(
      CC="icl",
      CXX="icl",
      SHCC="icl",
      SHCXX="icl",
      SHLINK="link",
    )
    env_etc.ccflags_base = """
      /nologo
      /D_SECURE_SCL=0
      /D_CRT_SECURE_NO_DEPRECATE
      /wd4996
      /Zm800
    """.split()
    env_etc.cxxflags_base = """
      /GR
      /EHsc
      /DBOOST_ALL_NO_LIB
    """.split()
    if (libtbx.env.build_options.optimization):
      opts = [
        "/DNDEBUG", "/Ob2", "/Ox", "/Oi", "/Ot", "/GT", "/MD", "/EHsc",
        "/fp:fast"]
      if (    libtbx.env.build_options.msvc_arch_flag is not None
          and not is_64bit_architecture):
        opts.append("/arch:%s" %libtbx.env.build_options.msvc_arch_flag)
    else:
      opts = ["/Od"]
    if (libtbx.env.build_options.debug_symbols):
      raise RuntimeError("Debug build not supported.")
    env_etc.ccflags_base.extend(opts)
    env_etc.shlinkflags = "/nologo /incremental:no /dll"
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
    env_etc.libm = []

  elif env_etc.compiler.endswith("_mingw"):
    if env_etc.compiler.startswith('win'):
      env_base = Environment(ENV=os.environ, tools=['mingw'])
    else:
      libtbx_dist_path = libtbx.env.under_dist(module_name="libtbx", path="")
      env_base = Environment(ENV=os.environ, tools=['crossmingw'],
                                             toolpath=[libtbx_dist_path])
      assert env_base['CC'] != "gcc", \
        "Cross-compiler gcc not found. How is it prefixed? (see crossmingw.py)"
    # the options here are mostly copied from unix_gcc
    env_etc.libm = ["m"]
    env_etc.static_libraries = 1
    env_etc.static_bpl = 0
    env_etc.gcc_version = libtbx.env_config.get_gcc_version(
                                                 command_name=env_base['CC'])
    env_etc.shlibsuffix = env_base['SHLIBSUFFIX']
    env_etc.c_link = env_base['CC']
    env_etc.ccflags_base = [
      "-fPIC",
      "-fno-strict-aliasing",
      "-DMS_WIN64"]
    env_etc.cxxflags_base = ["-DMS_WIN64"]
    # XXX feature duplication, see env.build_options.enable_cxx11
    if (env_etc.gcc_version >= 40400 and determine_cpp0x_flag()):
      env_etc.cxxflags_base.append("-std=c++0x")
      env_etc.cxx11_is_available = True
    if (libtbx.env.build_options.warning_level == 0):
      warn_options = ["-w"]
      env_etc._enable_more_warnings = "gcc"
    elif (libtbx.env.build_options.warning_level == 1):
      warn_options = gcc_common_warn_options()
    else:
      warn_options = gcc_common_warn_options() + ["-Werror"]
    env_etc.ccflags_base.extend(warn_options)
    if (libtbx.env.build_options.optimization):
      opts = ["-DNDEBUG", "-O3", "-ffast-math"]
      if (env_etc.gcc_version >= 40100 and is_64bit_architecture):
        opts.insert(2, "-funroll-loops")
    else:
      opts = ["-O0", "-fno-inline"]
    if (libtbx.env.build_options.mode == "profile"):
      opts.insert(0, "-pg")
      opts.insert(1, "-ggdb")
    elif (libtbx.env.build_options.debug_symbols):
      opts.insert(0, "-g")
    opts.append("-DBOOST_ALL_NO_LIB")
    env_etc.ccflags_base.extend(opts)
    if (static_exe):
      env_base.Prepend(LINKFLAGS=["-static"])
      static_exe = None
      lkfl = []
    elif libtbx.env.build_options.debug_symbols:
      lkfl = ["-rdynamic"]
    else:
      lkfl = ["-s"]
    env_etc.shlinkflags = ["-shared"]
    if (libtbx.env.build_options.mode == "profile"):
      env_base.Prepend(LINKFLAGS=["-pg"])
      env_etc.shlinkflags.append("-pg")
    env_base.Append(LINKFLAGS=lkfl)
    env_etc.shlinkflags.extend(lkfl)
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
    env_etc.mac_os_use_dsymutil = False

  else:
    sys.tracebacklimit = 0
    raise RuntimeError("Unknown compiler choice: %s" % env_etc.compiler)
else:
  env_etc.mac_cpu = None
  env_etc.mac_cpu_is_g4 = None
  env_etc.mac_os_version = None
  env_etc.mac_os_use_dsymutil = False
  if (sys.platform.startswith("darwin")):
    env_etc.mac_cpu = easy_run.fully_buffered(
      command="/usr/bin/uname -p") \
      .raise_if_errors() \
      .stdout_lines[0].strip()
    def mac_cpu_is_g4():
      if (env_etc.mac_cpu != "powerpc"): return False
      lines = easy_run.fully_buffered(
        command="/usr/sbin/system_profiler SPHardwareDataType") \
        .raise_if_errors() \
        .stdout_lines
      for line in lines:
        line = line.strip()
        if (   line.startswith("Machine Name: ")
            or line.startswith("Machine Model: ")
            or line.startswith("CPU Type: ")):
          if (line.find(" G4 ") > 0 or line.endswith(" G4")):
            return True
          break
      return False
    env_etc.mac_cpu_is_g4 = mac_cpu_is_g4()
    env_etc.mac_os_version = ".".join(easy_run.fully_buffered(
      command="/usr/bin/sw_vers -productVersion")
      .raise_if_errors()
      .stdout_lines[0].strip().split(".")[:2])
    if (env_etc.mac_os_version == "10.3"):
      os.environ["MACOSX_DEPLOYMENT_TARGET"] = env_etc.mac_os_version
    elif (env_etc.mac_os_version == "10.4"):
      pass
    elif (libtbx.env.build_options.debug_symbols):
      env_etc.mac_os_use_dsymutil = True
  env_base = Environment(
    ENV=os.environ,
    tools=['cc','g++','gnulink','ar'])
  set_python_include_and_libs(env_etc)
  if (sys.platform.startswith("darwin")):
    env_etc.shlibsuffix = ".dylib"
    env_etc.extension_module_suffix = ".so"
    env_base.Append(LIBSUFFIXES=[".dylib"])
  else:
    env_etc.shlibsuffix = ".so"
    env_etc.extension_module_suffix = ".so"
  env_etc.libm = []
  env_etc.libm.append("m")

  if (env_etc.compiler in ["unix_gcc", "unix_gcc4"]):
    env_etc.static_libraries = 0
    env_etc.static_bpl = 0
    cc = env_etc.compiler.replace("unix_", "")
    cxx = cc.replace("gcc", "g++")
    env_etc.gcc_version = libtbx.env_config.get_gcc_version(command_name=cc)
    env_base.Replace(
      CC=cc,
      SHCC=cc,
      CXX=cxx,
      LINK=cxx,
      SHCXX=cxx,
      SHLINK=cxx,
      SHLIBSUFFIX=env_etc.shlibsuffix,
    )
    env_etc.c_link = cc
    env_etc.ccflags_base = [
      "-fPIC",
      "-fno-strict-aliasing"]
    env_etc.cxxflags_base = []
    # XXX feature duplication, see env.build_options.enable_cxx11
    if (env_etc.gcc_version >= 40400 and determine_cpp0x_flag()):
      env_etc.cxxflags_base.append("-std=c++0x")
      env_etc.cxx11_is_available = True
    if (libtbx.env.build_options.warning_level == 0):
      warn_options = ["-w"]
      env_etc._enable_more_warnings = "gcc"
    elif (libtbx.env.build_options.warning_level == 1):
      warn_options = gcc_common_warn_options()
    else:
      warn_options = gcc_common_warn_options() + ["-Werror"]
    env_etc.ccflags_base.extend(warn_options)
    if (libtbx.env.build_options.optimization):
      opts = ["-DNDEBUG", "-O3"]
      if (env_etc.gcc_version < 11000):
        opts.append("-ffast-math")
      if (env_etc.gcc_version >= 40100 and is_64bit_architecture):
        opts.append("-funroll-loops")
    else:
      opts = ["-O0", "-fno-inline"]
    if (libtbx.env.build_options.mode == "profile"):
      opts.insert(0, "-pg")
      opts.insert(1, "-ggdb")
    elif (libtbx.env.build_options.debug_symbols):
      opts.insert(0, "-g")
    opts.append("-DBOOST_ALL_NO_LIB")
    env_etc.ccflags_base.extend(opts)
    if (static_exe):
      env_base.Prepend(LINKFLAGS=["-static"])
      static_exe = None
      lkfl = []

    elif libtbx.env.build_options.debug_symbols:
      lkfl = ["-rdynamic"]

    else:
      lkfl = ["-s"]
    env_etc.shlinkflags = ["-shared"]
    if (libtbx.env.build_options.mode == "profile"):
      env_base.Prepend(LINKFLAGS=["-pg"])
      env_etc.shlinkflags.append("-pg")
    env_base.Append(LINKFLAGS=lkfl)
    env_etc.shlinkflags.extend(lkfl)
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
  elif (env_etc.compiler == "unix_clang"):
    env_etc.static_libraries = 0
    env_etc.static_bpl = 0
    cc = "clang"
    cxx = "clang++"
    env_base.Replace(
      CC=cc,
      SHCC=cc,
      CXX=cxx,
      LINK=cxx,
      SHCXX=cxx,
      SHLINK=cxx,
      SHLIBSUFFIX=env_etc.shlibsuffix)

    conf = env_base.Configure()
    test_code = """
#include <iostream>
int main() {
  std::cout << __clang_major__ << ","
            << __clang_minor__ << ","
            << __clang_patchlevel__ << ",";
  return 0;
}
"""
    flag, output = conf.TryRun(test_code, extension='.cpp')
    conf.Finish()
    assert flag, "I have been asked to use the compiler clang but it is not working!"
    env_etc.clang_version = ()
    for x in output.split(','):
      if not x: x = None
      env_etc.clang_version += (x,)

    env_etc.c_link = cc
    env_etc.ccflags_base = [
      "-fPIC",
      "-fno-strict-aliasing"]
    env_etc.cxxflags_base = []
    setup_clang_warning(env_etc, libtbx.env.build_options.warning_level)
    if (libtbx.env.build_options.optimization):
      opts = [
        "-DNDEBUG",
        "-O3",
        ]
      # Detect clang problem with -ffast-math
      # (undefined macro __extern_always_inline)
      clone = env_base.Clone(
        CCFLAGS = env_etc.ccflags_base + [ "-ffast-math" ]
        )
      conf = clone.Configure()
      flag = conf.TryCompile( "#include <math.h>", extension='.cpp')
      conf.Finish()
      if flag:
        opts.append( "-ffast-math" )
    else:
      opts = ["-O0", "-fno-inline"]
    if (libtbx.env.build_options.debug_symbols):
      opts.insert(0, "-g")
    opts.append("-DBOOST_ALL_NO_LIB")
    env_etc.ccflags_base.extend(opts)
    if (static_exe):
      env_base.Prepend(LINKFLAGS=["-static"])
      static_exe = None
    env_etc.shlinkflags = ["-shared"]
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
  elif (env_etc.compiler == "unix_icc"):
    env_etc.static_libraries = 0
    env_etc.static_bpl = 0
    env_etc.icc_version = libtbx.env_config.get_gcc_version(command_name="icc")
    env_base.Replace(
      CC="icc",
      SHCC="icc",
      CXX="icpc",
      LINK="icpc",
      SHCXX="icpc",
      SHLINK="icpc",
    )
    env_etc.c_link = "icc"
    # Specific warnings disabled and why:
    # 161: unrecognized #pragma
    # 167: argument of type "type" is incompatible with parameter of type "type"
    #      Turned off in 2edd0; "incompatible char pointers, triggered by ccp4 code"
    env_etc.ccflags_base = [
      "-fPIC", "-fno-strict-aliasing", "-wd161,167"]
    env_etc.cxxflags_base = ["-Wno-deprecated"]
    # XXX feature duplication, see env.build_options.enable_cxx11
    if (env_etc.icc_version >= 100000 and determine_cpp0x_flag()):
      env_etc.cxxflags_base.append("-std=c++0x")
      env_etc.cxx11_is_available = True
    if (libtbx.env.build_options.optimization):
      opts = ["-DNDEBUG", "-O2"]
    else:
      opts = ["-O0"]
    if (libtbx.env.build_options.debug_symbols):
      opts.insert(0, "-g")
    opts.append("-DBOOST_ALL_NO_LIB")
    env_etc.ccflags_base.extend(opts)
    env_etc.shlinkflags = ["-shared"]
    env_etc.shlinkflags_bpl = list(env_etc.shlinkflags)
    if (static_exe):
      env_base.Prepend(LINKFLAGS=["-Bstatic"])
      static_exe = None
  elif (env_etc.compiler == 'unix_conda' or env_etc.compiler == 'darwin_conda'):
    # get conda compilers from environment
    # based on recommendation from conda-forge admins, just use CC and
    # CXX from environment for linux and macOS. No checks are done since
    # it is currently a full path on linux, but just a name on macOS
    cc = os.environ.get('CC', None)
    cxx = os.environ.get('CXX', None)

    if cc is not None or cxx is not None:
      print("Compiler settings:")
      if cc is not None:
        print("  CC: ", cc)
      if cxx is not None:
        print("  CXX:", cxx)
    if cc is None or cxx is None:
      print("Warning: CC and/or CXX has not been set in the environment.")

    # use environment flags if available
    if libtbx.env.build_options.use_environment_flags:
      env_etc.ccflags_base = []
      env_etc.cxxflags_base = []
      env_etc.shlinkflags = ['-shared']
      env_etc.shlinkflags_bpl = env_etc.shlinkflags
      if sys.platform.startswith('darwin'):
        env_etc.shlinkflags_bpl += ['-undefined', 'dynamic_lookup']
      if libtbx.env.build_options.cxxstd is not None:
        for cxxstd in ['c++11', 'c++14', 'c++17']:
          libtbx.env.build_options.env_cxxflags = \
            libtbx.env.build_options.env_cxxflags.replace(cxxstd, libtbx.env.build_options.cxxstd)
          libtbx.env.build_options.env_cppflags = \
            libtbx.env.build_options.env_cppflags.replace(cxxstd, libtbx.env.build_options.cxxstd)
      else:
        libtbx.env.build_options.env_cxxflags = \
          libtbx.env.build_options.env_cxxflags.replace('c++14', 'c++11').replace('c++17', 'c++11')
        libtbx.env.build_options.env_cppflags = \
          libtbx.env.build_options.env_cppflags.replace('c++14', 'c++11').replace('c++17', 'c++11')
      env_etc.static_libraries = 0
    else:
      env_etc.ccflags_base = ["-fPIC", "-fno-strict-aliasing"] + \
        gcc_common_warn_options() + \
        ["-DNDEBUG", "-O3", "-funroll-loops"] + \
        ["-DBOOST_ALL_NO_LIB"]
      if env_etc.compiler != 'darwin_conda':
        env_etc.ccflags_base.append("-ffast-math")
        # -ffast-math changes scitbx/lbfgs/tst_lbfgs_fem.py behavior in Xcode 11.4
        # minimization steps may be different than fortran standard
      env_etc.cxxflags_base = []
      env_etc.shlinkflags = ['-shared']
      env_etc.shlinkflags_bpl = env_etc.shlinkflags
      env_etc.static_libraries = 0
      if sys.platform == 'darwin':
        env_etc.static_libraries = 1
        env_etc.shlinkflags_bpl += ['-undefined', 'dynamic_lookup']
        min_macos = 10.9
        base_macos_flags = ['-stdlib=libc++',
                            '-mmacosx-version-min=%s' % min_macos]
        env_etc.ccflags_base.extend(base_macos_flags)
        env_etc.cxxflags_base.extend(base_macos_flags)
        env_etc.shlinkflags.extend(base_macos_flags)
        env_etc.shlinkflags_bpl.extend(base_macos_flags)
    env_etc.static_bpl = 0
    env_etc.c_link = cc
    env_etc.gcc_version = libtbx.env_config.get_gcc_version(command_name=cc)
    env_etc.cxx11_is_available = True

    if cc is not None:
      env_base.Replace(
        CC=cc,
        SHCC=cc,
      )
    if cxx is not None:
      env_base.Replace(
        CXX=cxx,
        LINK=cxx,
        SHCXX=cxx,
        SHLINK=cxx,
      )
    env_base.Replace(SHLIBSUFFIX=env_etc.shlibsuffix)

  elif env_etc.compiler.startswith("darwin"):
    cc  = env_etc.compiler.replace('darwin_','')
    if cc == "c++": cc = "cc"
    if cc == "gcc-4.2": cxx = "g++-4.2"
    elif cc == "cc": cxx = "c++"
    elif cc == "gcc": cxx = "g++"
    else: cxx = "%s++" % cc
    # darwin_c++ pretty much means Apple Compilers,
    # which we know are in /usr/bin
    # this is to survive the installation of e.g. gcc 4.7
    # that may install a soft link named c++ that shadows
    # Apple Compiler
    if env_etc.compiler == 'darwin_c++':
      cc, cxx = ["/usr/bin/" + p for p in (cc, cxx)]
    env_base.Replace(
      CC=cc,
      SHCC=cc,
      CXX=cxx,
      LINK=cxx,
      SHCXX=cxx,
      SHLINK=cxx,
      SHLIBSUFFIX=env_etc.shlibsuffix,
    )
    conf = env_base.Configure()
    test_code = [
      """#include <iostream>    """,
      """int main() {           """,
      """  std::cout << "{";    """,
    ]
    for macro in ("llvm",
                  "clang",
                  "clang_major", "clang_minor", "clang_patchlevel",
                  "GNUC", "GNUC_MINOR", "GNUC_PATCHLEVEL"
                  ):
      test_code.append("""  #ifndef __%s__\n  #define __%s__ ""\n  #endif """ % \
          ((macro,)*2))
      test_code.append("""  std::cout << "\\"%s\\":" << __%s__ << ", "; """
                       % ((macro,)*2))
    for macro in ("clang_version", "VERSION",):
      test_code.append("""  #ifndef __%s__\n  #define __%s__ ""\n  #endif """ %  \
          ((macro,)*2))
      test_code.append("""  std::cout << "\\"%s\\": \\"" << __%s__ << "\\", "; """
                       % ((macro,)*2))
    test_code.extend([
      """  std::cout << "}"; """,
      """  return 0;         """,
      """}                   """])
    flag, output = conf.TryRun("\n".join(test_code), extension='.cpp')
    conf.Finish()
    assert flag
    output = output.replace(":,", ":None,")
    macro = eval(output)
    if macro['clang'] is not None:
      env_etc.clang_version = (
        macro['clang_major'], macro['clang_minor'], macro['clang_patchlevel'])
    else:
      ### XXX inconsistency: on linux gcc_version is an int, not a tuple
      env_etc.gcc_version = (macro['GNUC'], macro['GNUC_MINOR'], macro['GNUC_PATCHLEVEL'])
      m = re.search(r"Apple Inc. build (\d+)\)", macro['VERSION'])
      if m: env_etc.apple_gcc_builds.gcc = int(m.group(1))
      if macro['llvm']:
        env_etc.gcc_is_llvm_gcc = True
        m = re.search(r"LLVM build ([\d.]+)\)", macro['VERSION'])
        if m: env_etc.apple_gcc_builds.llvm = int(m.group(1).split(".")[0])
      else:
        env_etc.gcc_is_llvm_gcc = False

    print("On MacOS, using ", end=' ')
    if env_etc.clang_version:
      if env_etc.clang_version[2] is not None:
        print("clang %i.%i.%i" % env_etc.clang_version)
      else:
        print("clang %i.%i" % env_etc.clang_version[:2])
    elif env_etc.gcc_version:
      print("gcc %i.%i.%i" % env_etc.gcc_version, end=' ')
      if env_etc.gcc_is_llvm_gcc:
        print("which is llvm-gcc", end=' ')
        if env_etc.apple_gcc_builds.llvm is not None:
          print(", LLVM build %i" % env_etc.apple_gcc_builds.llvm, end=' ')
    xcode_version = None
    try:
      # Print out the version of XCode (but only if we could check the version)
      xcode_version = subprocess.check_output(('xcodebuild', '-version'),
                                               stderr=subprocess.STDOUT)
      if hasattr(xcode_version, "decode"):
        xcode_version = xcode_version.decode()
      print(xcode_version)
      # store just the version of Xcode (e.g. 11.4 becomes [11, 4])
      try:
        for line in xcode_version.split('\n'):
          if line.startswith('Xcode'):
            xcode_version = [int(i) for i in line.split()[1].split('.')]
            break
      except Exception:
        pass
    except subprocess.CalledProcessError:
      print("Not using Xcode")
    print()

    env_etc.static_libraries = 1
    env_etc.static_bpl = 0

    min_macos = 10.9
    # check for Apple Silicon (only works for natively compiled Python)
    if platform.mac_ver()[-1] == 'arm64':
      min_macos = 11.0
    base_macos_flags = ['-stdlib=libc++', '-mmacosx-version-min=%s' % min_macos]

    link_flags = ["-w"] # suppress "source/lib does not exist" warning
    link_flags.extend(base_macos_flags)
    if libtbx.env.build_options.force_32bit:
      if (env_etc.gcc_version and env_etc.gcc_version >= 40201) or env_etc.clang_version:
        link_flags.extend(["-arch", "i386"])
    env_base.Replace(LINKFLAGS=link_flags)
    env_etc.c_link = "cc"
    env_etc.ccflags_base = [
      "-fPIC",
      "-fno-strict-aliasing"]
    env_etc.ccflags_base.extend(base_macos_flags)
    if env_etc.clang_version:
      setup_clang_warning(env_etc, libtbx.env.build_options.warning_level)
    else:
      if (libtbx.env.build_options.warning_level == 0):
        warn_options = ["-w"]
        env_etc._enable_more_warnings = "gcc"
      elif (libtbx.env.build_options.warning_level == 1):
        warn_options = gcc_common_warn_options()
      else:
        warn_options = gcc_common_warn_options() + ["-Werror"]
      env_etc.ccflags_base.extend(warn_options)
    if cxx == "c++" and env_etc.clang_version is None:
      env_etc.ccflags_base.extend(["-no-cpp-precomp"])
      if (env_etc.gcc_version < 40201):
        env_etc.ccflags_base.append("-Wno-long-double")

    env_etc.cxxflags_base = base_macos_flags

    if env_etc.clang_version is None and env_etc.gcc_version < 40000:
      env_etc.cxxflags_base.append("-fcoalesce-templates")
    env_etc.cxxflags_base.append("-DBOOST_ALL_NO_LIB")
    if (libtbx.env.build_options.optimization):
      opts = ["-DNDEBUG", "-O3"]
      # -ffast-math changes scitbx/lbfgs/tst_lbfgs_fem.py behavior in Xcode 11.4
      # minimization steps may be different than fortran standard
      if xcode_version is not None and xcode_version < [11, 4]:
        opts.append("-ffast-math")
      if (cxx == "c++" and env_etc.gcc_version
            and env_etc.gcc_version == 40000):
        opts[1] = "-O2" # Apple's optimizer is broken
      elif (cxx == "c++" and env_etc.gcc_version
              and env_etc.gcc_version == 40201
                and libtbx.env.build_options.force_32bit):
        opts[1] = "-O1" # Apple's optimizer is even more broken
      elif (libtbx.env.build_options.max_optimized):
        if env_etc.clang_version is None: opts[1] = "-fast"
        if (env_etc.mac_cpu_is_g4):
          opts.insert(1, "-mcpu=G4")
    else:
      opts = ["-O0", "-fno-inline"]
    if (libtbx.env.build_options.debug_symbols):
      opts.insert(0, "-g")
      if env_etc.compiler == "darwin_gcc-4.2":
        # The gcc 4.2 shipped by Apple with all versions of XCode
        # on Leopard is plagued by bug 27574:
        # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=27574
        # Workaround till Apple applies the patch that has already landed
        # on GCC 4.2 branch: use the old stabs debugging info format instead
        # of the new now native dwarf.
        # Beware: valgrind does not support stabs on Darwin: just use
        # gcc 4.0.1 for code to be tested by valgrind
        if env_etc.apple_gcc_builds.gcc < 5646:
          # fixed on 10.6 (Snow Leopard) whose Dev Tools start with that build
          opts.insert(0, "-gstabs+")
    env_etc.shlinkflags = base_macos_flags
    env_etc.shlinkflags_bpl = [
      "-w", # suppress "source/lib does not exist" warning
      "-bundle", "-undefined", "dynamic_lookup"]
    env_etc.shlinkflags_bpl.extend(base_macos_flags)
    if libtbx.env.build_options.force_32bit:
      if (env_etc.gcc_version and env_etc.gcc_version >= 40201) or env_etc.clang_version:
        env_etc.ccflags_base.extend(["-arch", "i386"])
        env_etc.cxxflags_base.extend(["-arch", "i386"])
        env_etc.shlinkflags.extend(["-arch", "i386"])
        env_etc.shlinkflags_bpl.extend(["-arch", "i386"])
    env_etc.ccflags_base.extend(opts)
    if (static_exe):
      # no action required
      static_exe = None
  else:
    sys.tracebacklimit = 0
    raise RuntimeError("Unknown compiler choice: %s" % env_etc.compiler)

# user options for any compiler from the environment variables
# at the time of configure
if( libtbx.env.build_options.use_environment_flags ):
  opts = libtbx.env.build_options
  print("libtbx.scons: using flags from initial environment: ")
  print("              CXXFLAGS = ", opts.env_cxxflags)
  print("              CFLAGS = ", opts.env_cflags)
  print("              CPPFLAGS = ", opts.env_cppflags)
  print("              LDFLAGS = ", opts.env_ldflags)
  flg = opts.env_cxxflags.split(" ")
  if( hasattr(env_etc, "cxxflags_base") ):
    env_etc.cxxflags_base.extend(flg)
  else:
    env_etc.cxxflags_base = flg
  flg = opts.env_cflags.split(" ")
  if( hasattr(env_etc, "ccflags_base") ):
    env_etc.ccflags_base.extend(flg)
  else:
    env_etc.ccflags_base = flg
  flg = opts.env_cppflags.split(" ")
  env_etc.ccflags_base.extend(flg)
  flg = opts.env_ldflags.split(" ")
  if( hasattr(env_etc, "shlinkflags") ):
    env_etc.shlinkflags.extend(flg)
  else:
    env_etc.shlinkflags = flg
  env_base.Append(LINKFLAGS=flg)
  env_base.Append(SHLINKFLAGS=flg)

# conda header and library paths
if (libtbx.env.build_options.use_conda):
  env_etc.conda_prefix = libtbx.env_config.get_conda_prefix()
  env_etc.conda_cpppath = [os.path.join(env_etc.conda_prefix, 'include')]
  env_etc.conda_libpath = [os.path.join(env_etc.conda_prefix, 'lib')]
  if (sys.platform == 'win32'):
    env_etc.conda_cpppath.append(
      os.path.join(env_etc.conda_prefix, 'Library', 'include'))
    env_etc.conda_libpath.extend([os.path.join(env_etc.conda_prefix, 'libs'),
                                  os.path.join(env_etc.conda_prefix, 'Library', 'lib'),
                                  os.path.join(env_etc.conda_prefix, 'Library', 'bin')])
  if (sys.platform == 'darwin'):
    env_etc.conda_shlinkflags = ['-headerpad_max_install_names']
    env_etc.conda_rpath = [
      os.path.join(env_etc.conda_prefix, 'lib'),
      os.path.join(abs(libtbx.env.build_path), 'lib')
    ]
    env_base.Append(SHLINKFLAGS=env_etc.conda_shlinkflags)
  env_base.Append(CPPPATH=env_etc.conda_cpppath)
  env_base.Append(LIBPATH=env_etc.conda_libpath)

# local build header and library paths
local_env = libtbx.env_config.get_local_env()
if local_env is not None:
  cpppath = [abs(p) for p in local_env.repository_paths]
  for module in local_env.module_list:
    for name in module.names_active():
      new_path = local_env.under_build(os.path.join(name, 'include'))
      if os.path.isdir(new_path):
        cpppath.append(new_path)
  if len(cpppath) > 0:
    env_base.Prepend(CPPPATH=cpppath)
  env_base.Prepend(LIBPATH=[os.path.join(abs(local_env.build_path), 'lib')])

# check again that only --cxxstd or --enable_cxx11 is set
if libtbx.env.build_options.enable_cxx11 \
  and libtbx.env.build_options.cxxstd is not None:
  raise Sorry('''
Both --enable_cxx11 and --cxxstd have been set. Please only set one of
these options.
''')

if libtbx.env.build_options.cxxstd is not None:

  if sys.platform == 'win32':
    cxx_flags = ['/std:%s' % libtbx.env.build_options.cxxstd]
  else:
    cxx_flags = ['-std=%s' % libtbx.env.build_options.cxxstd]

  # add flags
  env_etc.cxxflags_base.extend(cxx_flags)
  env_base.Append(CXXFLAGS=cxx_flags)
  env_base.Append(LINKFLAGS=cxx_flags)
  env_etc.shlinkflags.extend(cxx_flags)
  env_etc.shlinkflags_bpl.extend(cxx_flags)

  # enable compilation check later on
  env_etc.cxx11_is_available = True

# C++11 flags for any compiler
# XXX feature duplication, see determine_cpp0x_flag
if libtbx.env.build_options.enable_cxx11:
  def ver(tuple3):
    if tuple3 is None:
      return 0
    if type(tuple3) is not tuple:
      return tuple3
    t = tuple3[0]*100*100 + tuple3[1]*100
    if( len(tuple3)>2 ):
      t = t + tuple3[2]
    return t
  if env_etc.compiler=='darwin_c++' and ver(env_etc.clang_version)>=ver((4,1)):
    # version numbers are inconsisternt between Apple clang and original clang
    cxx11_flags = ["-std=c++11", "-stdlib=libc++",
                   "-mmacosx-version-min=%s" % min_macos]
  elif ver(env_etc.clang_version)>=ver((3,1)):
    cxx11_flags = ["-std=c++11", "-stdlib=libc++",
                   "-mmacosx-version-min=%s" % min_macos]
  elif ver(env_etc.gcc_version) >= ver((4,7)):
    cxx11_flags = ["-std=c++11"]
  elif ver(env_etc.gcc_version) >= ver((4,4)):
    cxx11_flags = ["-std=c++0x"]
  elif ver(env_etc.icc_version) >= ver((13,0)):
    # intel compiler on Mac needs C++11 enabled gcc "-gxx-name=g++-mp-4.7"
    # boost <=1.53 might need: "-D__GXX_EXPERIMENTAL_CPP0X__"
    cxx11_flags = ["-std=c++11"]
  elif ver(env_etc.icc_version) >= ver((10,0)):
    cxx11_flags = ["-std=c++0x"]
  elif env_etc.msvc_version and env_etc.msvc_version >= 11 :
    cxx11_flags = [] # no options are needed
  else:
    raise RuntimeError("Unknown C++11 flag for compiler: %s" % env_etc.compiler)
  env_etc.cxx11_is_available = True
  env_etc.cxxflags_base.extend(cxx11_flags)
  env_base.Append(CXXFLAGS=cxx11_flags)
  env_base.Append(LINKFLAGS=cxx11_flags)
  env_etc.shlinkflags.extend(cxx11_flags)
  env_etc.shlinkflags_bpl.extend(cxx11_flags)
if env_etc.cxx11_is_available:
  test_code  = "#include <iostream>\n"
  test_code += "#include <vector>\n"
  test_code += "#include <array>\n"
  test_code += "#include <utility>\n"
  test_code += "std::vector<int> func(std::vector<int> && in) "
  test_code += "{std::vector<int> r = std::move(in); return r;}\n"
  test_code += "int main() {std::vector<int> x(9); for(auto & i : x) "
  test_code += "{i=7; std::cout << i << std::endl;}\n"
  test_code += " x = func(std::vector<int>(2)); return 0;}\n"
  # 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:
    test_env = env_base.Clone(LIBPATH=[env_base['LIBPATH'][0]])
    conf = test_env.Configure()
  else:
    conf = env_base.Configure()
  flag, output = conf.TryRun(test_code, extension='.cpp')
  conf.Finish()
  if not flag:
    env_etc.cxx11_is_available = False
    if libtbx.env.build_options.cxxstd is not None:
      raise RuntimeError("{} is not available, see config.log".format(
        libtbx.env.build_options.cxxstd.capitalize()))
    elif libtbx.env.build_options.enable_cxx11:
      raise RuntimeError("C++11 is not available, see config.log")

""" ************************ Custom Builders and Emitters **************************** """


""" ****************** Change _CPPINCFLAGS ***********************************************
For each absolute path <path> in CPPPATH, we want -I<path> and only that.
By default, when <path> is inside the build directory, e.g. /path/to/cctbx_build/foo/bar,
SCons will add -Ifoo/bar and then -I<repository>/foo/bar for each <repository> declared
with Repository(...). This behaviour is welcome when <path> has the form #foo/bar but not
when <path> is an absolute path.
************************************************************************************** """

def _absolute_path_concat(prefix, list, suffix, env, f=lambda x: x, target=None,
    source=None):
  """ This is parrotted from _concat as defined in scons/src/engine/SCons/Defaults.py
  This does not handle the case when prefix has a trailing white space or suffix has a
  leading white space as SCons does with _concat.
  """
  if not list: return list
  new_list = []
  relatives = []
  for p in list:
    if os.path.isabs(p): new_list.append(p)
    else: relatives.append(p)
  from_relatives = f(SCons.PathList.PathList(relatives).subst_path(env, target, source))
  if from_relatives is not None: new_list.extend([ str(p) for p in from_relatives ])
  prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
  suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
  result = [ "%s%s%s" % (prefix, p, suffix) for p in new_list ]
  return result

env_base['_absolute_path_concat'] = _absolute_path_concat

env_base['_CPPINCFLAGS'] = \
  '$( ${_absolute_path_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)'

""" ********************************************************************************** """

if ARGUMENTS.get('TERSE') == '1':
  compiling_message = "Compiling $TARGET"
  linking_message   = "Linking $TARGET"
  env_base['CCCOMSTR']     = compiling_message
  env_base['CXXCOMSTR']    = compiling_message
  env_base['LINKCOMSTR']   = linking_message
  env_base['SHCCCOMSTR']   = compiling_message
  env_base['SHCXXCOMSTR']  = compiling_message
  env_base['SHLINKCOMSTR'] = linking_message
  env_base['ARCOMSTR']     = "Archiving $TARGET"

env_base.Append(LIBPATH=["#lib"])
env_base.Replace(SHOBJSUFFIX=env_base["OBJSUFFIX"])

# XXX backward compatibility 2009-08-21
# future: remove ccflags_base, cxxflags_base from env_etc
def env_base_sync_with_env_etc():
  env_base.Replace(
    CFLAGS=env_etc.cflags_base,
    CCFLAGS=env_etc.ccflags_base,
    CXXFLAGS=env_etc.cxxflags_base,
    SHCFLAGS=env_etc.cflags_base,
    SHCCFLAGS=env_etc.ccflags_base,
    SHCXXFLAGS=env_etc.cxxflags_base)
env_base_sync_with_env_etc()

if (static_exe):
  sys.tracebacklimit = 0
  raise RuntimeError("Static executables not supported on this platforms.")

if (env_etc.static_libraries == 0
    and libtbx.env.build_options.static_libraries):
  env_etc.static_libraries = 1
env_etc.static_exe = libtbx.env.build_options.static_exe

env_etc.libtbx_build = libtbx.env.build_path

env_etc.base_include = libtbx.env.under_base("include")
env_etc.base_lib = libtbx.env.under_base("lib")

env_etc.libtbx_include = libtbx.env.under_build("include")
env_etc.libtbx_lib = abs(libtbx.env.lib_path)

def enable_openmp_if_possible():
  test_code = r"""%s
#include <iostream>
#include <iomanip>
int main() {
  double e, pi;
  const int N=100000;
  #pragma omp parallel sections shared(e, pi)
  {
    #pragma omp section
    {
      e = 1;
      double a = 1;
      for(int i=1; i<N; ++i) {
        a /= i;
        e += a;
      }
    }
    #pragma omp section
    {
      pi = 0;
      double a=1, b=3;
      for(int i=1; i<2*N; ++i) {
        pi += 1/a - 1/b;
        a += 4;
        b += 4;
      }
      pi *= 4;
    }
  }
  std::cout << std::setprecision(6) << "e=" << e << ", pi=" << pi << "\n";
}
""" % ['', '#include <omp.h>'][int(sys.platform == 'win32')]
  #
  env_etc.have_openmp = False
  if (env_etc.compiler == "win32_cl" and env_base["MSVC_VERSION"] == "7.1"):
    print("libtbx.scons: OpenMP is not available.")
    return
  if env_etc.compiler == "darwin_clang":
    print("libtbx.scons: OpenMP is not supported by clang yet.")
    return
  if env_etc.compiler == "darwin_gcc-4.2" :
    print("libtbx.scons: OpenMP implementation broken.")
    return
  if (not libtbx.env.build_options.enable_openmp_if_possible):
    print("libtbx.scons: OpenMP is disabled.")
    return
  from libtbx.utils import select_matching
  compiler_options, linker_options = select_matching(
    key=env_etc.compiler,
    choices=[
      ('^win32_cl$',  [['/openmp'], []]),
      ('^win32_icc$', [['/Qopenmp']]*2),
      ('^unix_icc$',  [['-openmp']]*2),
      ('^unix_conda$',[['-fopenmp']]*2),
      ('darwin',      [['-fopenmp']]*2),
      ('gcc',         [['-fopenmp']]*2),
      ('mingw',       [['-fopenmp']]*2),
    ],
    default=[None]*2)
  # Intel compiler > 15 deprecates -openmp in favor of -qopenmp
  if env_etc.compiler == "unix_icc" and env_etc.icc_version >= 150000:
    compiler_options, linker_options = [["-qopenmp"], ["-qopenmp"]]

  if (compiler_options is None):
    print("libtbx.scons: OpenMP is not supported.")
    return
  env = env_base.Clone()
  env.Append(CCFLAGS=compiler_options)
  env.Append(LINKFLAGS=linker_options)
  conf = env.Configure()
  flag, output = conf.TryRun(test_code, extension='.cpp')
  conf.Finish()
  if (not flag or output.strip() != "e=2.71828, pi=3.14159"):
    print("libtbx.scons: OpenMP is not usable.")
    return
  msg = "libtbx.scons: OpenMP is not usable (in shared libraries)."
  if (    sys.platform.startswith('linux')
      and not env_etc.no_boost_python
      and env_etc.gcc_version is not None
      and env_etc.gcc_version < 40300):
    # C.f. http://gcc.gnu.org/bugzilla/show_bug.cgi?id=28482
    print(msg)
    return
  print("libtbx.scons: OpenMP is available.")
  env_etc.have_openmp = True
  env_etc.ccflags_base.extend(compiler_options)
  env_base.Append(LINKFLAGS=linker_options)
  env_etc.shlinkflags.extend(linker_options)
  env_etc.shlinkflags_bpl.extend(linker_options)
  env_base_sync_with_env_etc()

def enable_cuda_if_possible():
  env_etc.enable_cuda = False
  if (libtbx.env.build_options.enable_cuda):

    cuda_version = easy_run.fully_buffered(command='nvcc --version',
                                           join_stdout_stderr=True).stdout_lines
    if ('command not found' in cuda_version[0]):
      raise RuntimeError('nvcc was not found')
    for line in cuda_version:
      if ('release' in line):
        break

    major = None
    cuda_version = re.findall('release [0-9]+\.*[0-9]+', line)
    if cuda_version:
      try:
        assert( len(cuda_version)==1)
        cuda_version = cuda_version[0].split("release")[1].strip()
        cuda_version = float(cuda_version)
        major = int(cuda_version)
      except (AssertionError, ValueError, IndexError):
        pass
    if ( (major is None) or (major < 4) ):
      raise RuntimeError(
        'CUDA Toolkit 4.0 and higher is required.'
        ' Current CUDA version: %s' % line)
    env_etc.enable_cuda = True

    # generate compute flags based on version
    # these are flags for base version
    if cuda_version >=6 and cuda_version <= 7.5:
        arch_flags = ['--generate-code=arch=compute_30,code=sm_30',
                        '--generate-code=arch=compute_32,code=sm_32',
                        '--generate-code=arch=compute_35,code=sm_35',
                        '--generate-code=arch=compute_50,code=sm_50']

    elif cuda_version == 8:
        arch_flags = ['--generate-code=arch=compute_30,code=sm_30',
                        '--generate-code=arch=compute_32,code=sm_32',
                        '--generate-code=arch=compute_35,code=sm_35',
                        '--generate-code=arch=compute_50,code=sm_50',
                        '--generate-code=arch=compute_52,code=sm_52',
                        '--generate-code=arch=compute_53,code=sm_53',
                        '--generate-code=arch=compute_60,code=sm_60',
                        '--generate-code=arch=compute_61,code=sm_61',
                        '--generate-code=arch=compute_62,code=sm_62']

    elif cuda_version >= 9 and cuda_version <= 9.2:
        arch_flags = ['--generate-code=arch=compute_30,code=sm_30',
                        '--generate-code=arch=compute_32,code=sm_32',
                        '--generate-code=arch=compute_35,code=sm_35',
                        '--generate-code=arch=compute_50,code=sm_50',
                        '--generate-code=arch=compute_52,code=sm_52',
                        '--generate-code=arch=compute_53,code=sm_53',
                        '--generate-code=arch=compute_60,code=sm_60',
                        '--generate-code=arch=compute_61,code=sm_61',
                        '--generate-code=arch=compute_62,code=sm_62',
                        '--generate-code=arch=compute_70,code=sm_70',
                        '--generate-code=arch=compute_72,code=sm_72']

    elif cuda_version >= 10 and cuda_version <= 10.2:
        arch_flags = ['--generate-code=arch=compute_35,code=sm_35',
                        '--generate-code=arch=compute_50,code=sm_50',
                        '--generate-code=arch=compute_52,code=sm_52',
                        '--generate-code=arch=compute_53,code=sm_53',
                        '--generate-code=arch=compute_60,code=sm_60',
                        '--generate-code=arch=compute_61,code=sm_61',
                        '--generate-code=arch=compute_62,code=sm_62',
                        '--generate-code=arch=compute_70,code=sm_70',
                        '--generate-code=arch=compute_72,code=sm_72',
                        '--generate-code=arch=compute_75,code=sm_75']

    elif cuda_version >= 11:  # NOTE: lower compute compat. are being phased out
        arch_flags = ['--generate-code=arch=compute_60,code=sm_60',
                      '--generate-code=arch=compute_61,code=sm_61',
                      '--generate-code=arch=compute_62,code=sm_62',
                      '--generate-code=arch=compute_70,code=sm_70',
                      '--generate-code=arch=compute_72,code=sm_72',
                      '--generate-code=arch=compute_75,code=sm_75',
                      '--generate-code=arch=compute_80,code=sm_80',
                      '--generate-code=arch=compute_86,code=sm_86']
        # cuda 11.0 does not support compute_86, remove last entry
        if cuda_version < 11.1:
            arch_flags.pop()
    else:
        raise Sorry("cctbx only supports cuda versions 6-11")

    env_base['NVCC'] = 'nvcc'
    linker_flags = env_base['SHLINKFLAGS']
    for flag in linker_flags:
      if (flag.find(' ') >= 0):
        raise RuntimeError(
          'Not implemented: wrapping of linker flags with'
          ' embedded spaces: %s' % flag)
    linker_flags = ' '.join(linker_flags)
    env_base['NVCCSHLINKFLAGS'] = ['-shared'] #,'--linker-options',linker_flags]

    cc_flags = env_base['CCFLAGS']
    for flag in cc_flags:
      if (flag.find(' ') >= 0):
        raise RuntimeError(
          'Not implemented: wrapping of compiler flags with'
          ' embedded spaces: %s' % flag)
    cc_flags = ' '.join(cc_flags)
    env_base['NVCCFLAGS'] = arch_flags + \
                             ['-use_fast_math','--shared',
                             '--compiler-options',cc_flags]

    shared_object_builder =\
      Builder\
      (action = '$NVCC -c -o $TARGET $_CPPINCFLAGS $NVCCFLAGS $SOURCES',
       suffix = '.o',
       src_suffix = ['.cu','.cpp'],
       source_scanner = SCons.Scanner.C.CScanner())
    shared_library_builder =\
      Builder\
      (action ='$NVCC -o $TARGET $NVCCSHLINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS ' \
          '$_LIBFLAGS',
       suffix = '$SHLIBSUFFIX',
       src_suffix = '.o')

    env_base['BUILDERS']['cudaSharedLibrary'] = shared_library_builder
    env_base['BUILDERS']['cudaSharedLibrary'].add_src_builder(shared_object_builder)

def enable_kokkos_if_possible():
  env_etc.enable_kokkos = False
  if (libtbx.env.build_options.enable_kokkos):
    if sys.version_info.major == 2:
      raise RuntimeError('Kokkos is not available on Python 2')
    env_etc.enable_kokkos = True

enable_openmp_if_possible()
enable_cuda_if_possible()
enable_kokkos_if_possible()

if os.environ.get('LIBTBX_COMPILATION_DB', False):
  env_base.Tool('compilation_db')
  env_base.CompilationDatabase('compilation_db.json')

Export("env_base", "env_etc")
