# file      : libbuild2/buildfile
# license   : MIT; see accompanying LICENSE file

# NOTE: remember to update bundled_modules in libbuild2/module.cxx if adding a
#       new module.
#
bundled_modules = bash/ bin/ c/ cc/ cli/ cxx/ in/ version/

./: lib{build2} $bundled_modules

# Note that we have to load these buildfiles explicitly in order to have their
# imports processed before the $config.save() call below. Failed that, we will
# get a warning about saving unused config.import.* values.
#
# Note also that each module buildfile in turn include us (in case it is built
# directly) thus causing an include cycle. However, this cycle is harmless
# since all it needs is the definition of the lib{build2} target and only
# during match/execute, by which time the rest of this buildfile would have
# been loaded.
#
include [allow_cycle] $bundled_modules

# A module should treat lib{build2} as an "implied interface dependency"
# meaning that it can link it as an implementation dependency and assume that
# whomever imports and links this module will also import and link lib{build2}
# explicitly. A module should also assume that lib{butl} will always be an
# interface dependency of lib{build2} and therefore need not be explicitly
# imported or linked.

# NOTE: shared imports should go into root.build.
#
intf_libs = $libbutl

lib{build2}: libul{build2}:               \
  {hxx ixx txx cxx}{* -utility-*installed \
                      -common-options     \
                      -b-options          \
                      -config             \
                      -version            \
                      -*.test...}         \
  {hxx ixx cxx}{common-options}           \
  {hxx ixx cxx}{b-options}                \
  {hxx}{config version}

libul{build2}: script/{hxx ixx txx cxx}{** -*-options -**.test...} \
               script/{hxx ixx cxx}{builtin-options}

libul{build2}: build/script/{hxx ixx txx cxx}{** -*-options -**.test...} \
               build/script/{hxx ixx cxx}{builtin-options}

libul{build2}: shell/script/{hxx ixx txx cxx}{** -*-options -**.test...} \
               shell/script/{hxx ixx cxx}{builtin-options}

# Note that this won't work in libul{} since it's not installed.
#
lib{build2}: cxx{utility-installed}:   for_install = true
lib{build2}: cxx{utility-uninstalled}: for_install = false

# These are "core modules" that come bundled with libbuild2 (see also unit
# tests loop below). Note that the build system core can still function
# without them or with their alternative implementations. Also note that
# config/utility.?xx are part of the build system core (see comments in the
# header for details).
#
# NOTE: remember to update import_modules() in libbuild2/modules.cxx if adding
#       a new such module.
#
libul{build2}: config/{hxx ixx txx cxx}{** -host-config -**.test...} \
               config/cxx{host-config}

# Derive ~host and ~build2 configurations from current configuration.
#
# This will of course blow up spectacularly if we are cross-compiling. But
# let's wait and enjoy the fireworks (and get a sense of why someone would
# want to cross-compile a build system).
#
# For the ~host configuration we only want c/cxx/cc and bin that they load.
# For ~build2 we want to keep everything except config.dist.* and
# config.build2.* (none of them currently affect the interface).
#
# We also remove comment lines which could be confused with preprocessor
# directives by some lesser compilers and blank lines between groups of
# options which could cause spurious rebuilds when we filter out entire
# groups.
#
# For ~host also filter out config.bin.lib/config.bin.*.lib (static/shared
# library build/link preferences). In particular, we don't want to force
# config.bin.lib=shared since that will cause static libraries to link shared
# versions of their prerequisites (see mysql-client for a case where this can
# make a difference). Also sanitize config.bin.rpath*.
#
# For ~build2 also filter out config.install.chroot -- we definitely don't
# want it carried through. Also filter out variables that control tests
# execution.
#
# Finally, for both ~host and ~build2 we keep config.config.environment
# but strip config.config.hermetic* (we shouldn't be forcing hermiticity
# on the users of ~host/~build2; they can decide for themselves if they
# want it).
#
# The *_no_warnings variants are with the suppressed C/C++ compiler warnings
# (in particular, used for private host configuration in bpkg).
#
#
host_config_lines = [strings]
build2_config_lines = [strings]

host_config_no_warnings_lines = [strings]
build2_config_no_warnings_lines = [strings]

for l: $regex.replace_lines(                                                            \
  $config.save(),                                                                       \
  '^( *(#|(config\.(build2\.|test[. ]|dist\.|install\.chroot|config\.hermetic))).*|)$', \
  [null])
{
  if ($defined(config.build2.host_c) && $regex.match($l, ' *config\.c[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_c)")

  if ($defined(config.build2.host_cxx) && $regex.match($l, ' *config\.cxx[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_cxx)")

  # config.c.[pcl]options
  #
  if ($defined(config.build2.host_c_poptions) && \
      $regex.match($l, ' *config\.c\.poptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_c_poptions)")

  if ($defined(config.build2.host_c_coptions) && \
      $regex.match($l, ' *config\.c\.coptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_c_coptions)")

  if ($defined(config.build2.host_c_loptions) && \
      $regex.match($l, ' *config\.c\.loptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_c_loptions)")

  # config.cxx.[pcl]options
  #
  if ($defined(config.build2.host_cxx_poptions) && \
      $regex.match($l, ' *config\.cxx\.poptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_cxx_poptions)")

  if ($defined(config.build2.host_cxx_coptions) && \
      $regex.match($l, ' *config\.cxx\.coptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_cxx_coptions)")

  if ($defined(config.build2.host_cxx_loptions) && \
      $regex.match($l, ' *config\.cxx\.loptions[ =].*'))
    l = $regex.replace($l, '=.*$', "= $quote($config.build2.host_cxx_loptions)")

  # Note: also preserve config.version.
  #
  h = [null]
  if $regex.match(                                                          \
    $l,                                                                     \
    ' *config\.(c[. ]|cxx[. ]|cc[.]|bin[.]|config.environment |version ).*')
  {
    if! ($regex.match(\
           $l,        \
           ' *config\.bin\.(lib|exe\.lib|liba\.lib|libs\.lib)[ =].*'))
    {
      # Filter out sanitizer options in ~host. We run the toolchain with
      # various sanitizers on CI but sanitizers cause issues in some packages.
      # Note that we can have both -fsanitize and -fno-sanitize forms. For
      # example:
      #
      # -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all
      #
      if $regex.match($l, ' *config\.(c|cxx|cc)\.(coptions|loptions)[ =].*')
      {
        h = $regex.replace($l, ' ?-f(no-)?sanitize[=-][^ ]+', '')
      }
      # Sanitize/override config.bin.{rpath,rpath_link}.
      #
      elif $regex.match($l, ' *config\.bin\.rpath[ =].*')
      {
        h = $regex.replace($l,     \
                           '=.*$', \
                           ($defined(config.build2.host_rpath)      \
                            ? "= $quote($config.build2.host_rpath)" \
                            : '= [null]'))
      }
      elif $regex.match($l, ' *config\.bin\.rpath_link[ =].*')
      {
        h = $regex.replace($l, '=.*$', '= [null]')
      }
      else
        h = $l
    }
  }

  ifn! $h
    host_config_lines += $h

  build2_config_lines += $l

  # Append the warning suppressing option to config.{c,cxx}.coptions rather
  # than config.cc.coptions since the former could re-enable them.
  #
  if ($regex.match($l, ' *config\.(c|cxx)\.coptions[ =].*'))
  {
    # Note that in MSVC overriding one warning option (say /W3) with another
    # (say /w) triggers a warning. However, our compile_rule sanitizes the
    # command line to resolve such overrides (see msvc_sanitize_cl()).
    #
    o = ($cxx.class == 'gcc' ? -w : $cxx.class == 'msvc' ? /w : )

    if ($regex.match($l, '[^=]+= *\[null\] *'))
    {
      l = $regex.replace($l, '= *\[null\] *$', "= $o")
      h = $regex.replace($h, '= *\[null\] *$', "= $o")
    }
    else
    {
      l = $regex.replace($l, '=(.*)$', "=\\1 $o")
      h = $regex.replace($h, '=(.*)$', "=\\1 $o")
    }
  }

  ifn! $h
    host_config_no_warnings_lines += $h

  build2_config_no_warnings_lines += $l
}

config/cxx{host-config}: config/in{host-config}
{
  host_config   = $regex.merge($host_config_lines,   '(.+)', '\1\n')
  build2_config = $regex.merge($build2_config_lines, '(.+)', '\1\n')

  host_config_no_warnings   = $regex.merge($host_config_no_warnings_lines,   \
                                           '(.+)', '\1\n')
  build2_config_no_warnings = $regex.merge($build2_config_no_warnings_lines, \
                                           '(.+)', '\1\n')
}

libul{build2}: dist/{hxx ixx txx cxx}{** -**.test...}

libul{build2}: install/{hxx ixx txx cxx}{** -**.test...}

libul{build2}: test/{hxx ixx txx cxx}{** -**.test...}

libul{build2}: $intf_libs

# Include the generated config and version headers into the distribution (so
# that we don't pick up installed ones) and don't remove them when cleaning in
# src (so that clean results in a state identical to distributed).
#
hxx{config}: in{config}
hxx{version}: in{version} $src_root/manifest

hxx{config version}:
{
  dist  = true
  clean = ($src_root != $out_root)
}

# Unit tests.
#
exe{*.test}:
{
  test = true
  install = false
}

for t:         cxx{ *.test...} \
        script/cxx{**.test...} \
         build/cxx{**.test...} \
         shell/cxx{**.test...} \
        config/cxx{**.test...} \
          dist/cxx{**.test...} \
       install/cxx{**.test...} \
          test/cxx{**.test...}
{
  d = $directory($t)
  n = $name($t)...
  b = $path.base($name($t))

  ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n +$b+*.test...}
  $d/exe{$n}: libul{build2}: bin.whole = false
  $d/exe{$n}: cxx{utility-uninstalled}
}

# Build options.
#
# NOTE: this scope happens to be outer to the bundled modules which means they
#       will inherit any scope-wide options. So we should normally only set
#       them on targets.
#
obja{*}: cxx.poptions += -DLIBBUILD2_STATIC_BUILD
objs{*}: cxx.poptions += -DLIBBUILD2_SHARED_BUILD

# Pass our compiler target to be used as build2 host and our out_root to be
# used as the build system import path (unless cross-compiling and not
# forgetting to escape backslashes on Windows).
#
{obja objs}{context}: cxx.poptions += "-DBUILD2_HOST_TRIPLET=\"$cxx.target\""

# Note that we used to compare complete target triplets but that proved too
# strict. For example, we may be running on x86_64-apple-darwin17.7.0 while
# the compiler is targeting x86_64-apple-darwin17.3.0.
#
cross = ($cxx.target.cpu != $build.host.cpu || \
         $cxx.target.system != $build.host.system)

if! $cross
{
  {obja objs}{context}: cxx.poptions += \
    -DBUILD2_IMPORT_PATH=\"$regex.replace($out_root, '\\', '\\\\')\"
}

# Note that while the -installed object file should only be linked when we
# are installing, it will be compiled even in the uninstalled case.
#
ifn! $install.root
{
  # Only if installed.
  #
  {obja objs}{utility-installed}: cxx.poptions += \
    -DBUILD2_INSTALL_LIB=\"$regex.replace(\
      $install.resolve($install.lib, \
                       $install.relocatable ? $install.bin : [dir_path]), \
                       '\\', \
                       '\\\\')\"

  # Only if configured.
  #
  # Note: strip the last directory component (<project>).
  #
  {obja objs}{utility-installed utility-uninstalled}: cxx.poptions += \
    -DBUILD2_INSTALL_BUILDFILE=\"$regex.replace(\
      $directory(\
        $install.resolve(\
          $install.buildfile, \
          $install.relocatable ? $install.bin : [dir_path])), '\\', '\\\\')\"

  # Data directory or src_root if not installed.
  #
  # Note: normalized in both cases.
  #
  # NOTE: currenly unused.
  #
  #\
  {obja objs}{utility-installed}: cxx.poptions += \
    -DBUILD2_INSTALL_DATA=\"$regex.replace(\
      $install.resolve($install.data, \
                       $install.relocatable ? $install.bin : [dir_path]), \
      '\\', \
      '\\\\')\"

  {obja objs}{utility-uninstalled}: cxx.poptions += \
    -DBUILD2_INSTALL_DATA=\"$regex.replace(\
      $src_root, '\\', '\\\\')\"
  #\

  # Only if installed and is relocatable.
  #
  if $install.relocatable
  {
    {obja objs}{utility-installed}: cxx.poptions += \
      -DBUILD2_INSTALL_ROOT=\"$regex.replace($install.root, '\\', '\\\\')\"

    {obja objs}{utility-installed}: cxx.poptions += \
      -DBUILD2_INSTALL_ROOT_RELATIVE=\"$regex.replace(\
        $path.relative($install.root, \
                       $install.resolve($install.bin, [dir_path])), \
        '\\', \
        '\\\\')\"
  }
}

if ($cxx.target.class != 'windows')
{
  libul{build2}: cxx.libs += -pthread

  # Note: only linking libdl in shared build.
  #
  if ($cxx.target.class != "bsd")
    libus{build2}: cxx.libs += -ldl
}
else
{
  # @@ TMP work around Clang bug #45021.
  #
  if ($cxx.id == 'clang' && $cxx.target.system == 'win32-msvc')
  {
    if ($regex.find_match($cc.coptions $cxx.coptions, '-O[23]'))
      script/obj{run}: cxx.coptions += -O1
  }
}

# Export options.
#
lib{build2}:
{
  cxx.export.poptions = "-I$out_root" "-I$src_root"
  cxx.export.libs = $intf_libs

  # Export interface version to be used by build system modules.
  #
  # Note: see also build_version_interface in utility.cxx.
  #
  interface_version = ($version.pre_release  \
                       ? $version.project_id \
                       : "$version.major.$version.minor")
}

# While we don't call any pthread_*() functions in our API, this appears to be
# needed for some std::thread implementations (like libstdc++).
#
if ($cxx.target.class != 'windows')
  lib{build2}: cxx.export.libs += -pthread

liba{build2}: cxx.export.poptions += -DLIBBUILD2_STATIC
libs{build2}: cxx.export.poptions += -DLIBBUILD2_SHARED

# For pre-releases use the complete version to make sure they cannot be used
# in place of another pre-release or the final version. See the version module
# for details on the version.* variable values.
#
if $version.pre_release
  lib{build2}: bin.lib.version = @"-$version.project_id"
else
  lib{build2}: bin.lib.version = @"-$version.major.$version.minor"

# Generated options parser.
#
# Note that the cli runtime namespace is build2::build::cli rather than
# build2::cli. That's because the cli namespace inside build2 is reserved for
# the cli build system module (libbuild2-cli). In fact, every namespace inside
# build2 is reserved for a potential module and the only namespace names we
# can use are build (this name, along with import and export, is reserved by
# the build system core) and names that start with an underscore.
#
if $cli.build.configured
{
  cli.options += --std c++11 -I $src_root --include-with-brackets \
--cli-namespace build2::build::cli --generate-specifier

  cli.cxx{*}:
  {
    # Include the generated cli files into the distribution and don't remove
    # them when cleaning in src (so that clean results in a state identical
    # to distributed).
    #
    dist  = true
    clean = ($src_root != $out_root)

    # We keep the generated code in the repository so copy it back to src in
    # case of a forwarded configuration.
    #
    backlink = overwrite
  }

  cli.cxx{common-options}: cli{common}
  {
    cli.options += --include-prefix libbuild2 --guard-prefix LIBBUILD2 \
--export-symbol LIBBUILD2_SYMEXPORT \
--hxx-prologue '#include <libbuild2/export.hxx>' \
--generate-file-scanner --generate-vector-scanner
  }

  cli.cxx{b-options}: cli{b}
  {
    cli.options += --include-prefix libbuild2 --guard-prefix LIBBUILD2 \
--export-symbol LIBBUILD2_SYMEXPORT \
--hxx-prologue '#include <libbuild2/export.hxx>' \
--cxx-prologue "#include <libbuild2/types-parsers.hxx>" \
--keep-separator --generate-parse --generate-merge

    # Usage options.
    #
    cli.options += --suppress-undocumented --long-usage --ansi-color \
--ascii-tree --page-usage 'build2::print_$name$_' --option-length 23
  }

  script/cli.cxx{builtin-options}: script/cli{builtin}
  {
    cli.options += --include-prefix libbuild2/script \
--guard-prefix LIBBUILD2_SCRIPT --generate-modifier --suppress-usage

    # Don't install the generated cli headers since they are only used
    # internally in the script implementation.
    #
    install = false
  }

  build/script/cli.cxx{builtin-options}: build/script/cli{builtin}
  {
    cli.options += --include-prefix libbuild2/build/script \
--guard-prefix LIBBUILD2_BUILD_SCRIPT \
--cxx-prologue "#include <libbuild2/types-parsers.hxx>" \
--generate-parse --generate-modifier --suppress-usage

    # Don't install the generated cli headers since they are only used
    # internally in the buildscript implementation.
    #
    install = false
  }

  shell/script/cli.cxx{builtin-options}: shell/script/cli{builtin}
  {
    cli.options += --include-prefix libbuild2/shell/script \
--guard-prefix LIBBUILD2_SHELL_SCRIPT --suppress-usage

    # Don't install the generated cli headers since they are only used
    # internally in the buildscript implementation.
    #
    install = false
  }
}
else
{
  # No install for the pre-generated case.
  #
  script/hxx{builtin-options}@script/ \
  script/ixx{builtin-options}@script/: install = false

  build/script/hxx{builtin-options}@build/script/ \
  build/script/ixx{builtin-options}@build/script/: install = false

  shell/script/hxx{builtin-options}@shell/script/ \
  shell/script/ixx{builtin-options}@shell/script/: install = false
}

# Install into the libbuild2/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
  install         = include/libbuild2/
  install.subdirs = true
}
