#! /usr/bin/perl
# -*- CPerl -*-
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
    & eval 'exec perl -S $0 $argv:q'
    if 0;

# configure script for OpenDDS
# Distributed under the OpenDDS License.
# See: http://www.opendds.org/license.html

use strict;
use warnings;

use Getopt::Long;
use Dumpvalue;
use File::Spec;
use File::Basename;
use File::Copy;
use File::Path;
use File::Temp ();
use FileHandle;
use Cwd;
use POSIX qw(strftime);

use FindBin;
use lib "$FindBin::RealBin/tools/scripts/modules";
use command_utils;
use ChangeDir;

## Version of OCI's TAO to download
my $oci_tao_version = '2.2a';
## Version of DOC Group ACE/TAO to download, uses the ACE version number
my $doc_tao2_version = '6.5.17';
my $doc_tao3_version = '7.0.8';

my $backup_timestamp = strftime "%Y-%m-%d-%H-%M-%S", localtime time;

# save args before Getopt modifies them
my @ARGS = @ARGV;

sub perlOS_to_host {
  return 'win32' if $^O eq 'MSWin32';
  return 'macosx' if $^O eq 'darwin';
  return $^O;
}

sub perlOS_to_java_platform {
  return 'win32' if $^O eq 'MSWin32';
  return $^O;
}

my $targetUsageIndent = "\t\t";

my %platforminfo =
  ('win32' => {
               'compilers' => ['cl'],
               'libpath' => 'PATH',
               'cl_versions' => {13.1 => 'vc71', 14 => 'vc8', 15 => 'vc9',
                                 16 => 'vc10', 17 => 'vc11', 18 => 'vc12',
                                 19 => 'vc14', 19.1 => 'vs2017',
                                 19.2 => 'vs2019', 19.3 => 'vs2022'},
               'cl_archs' => {'x64' => 1, 'Win32' => 1, 'ARM64' => 0, 'ARM' => 0}, # 1 if supported
              },
   'macosx' => {
                'compilers' => ['clang++'],
                'libpath' => 'DYLD_LIBRARY_PATH',
               },
   'solaris' => {
                 'compilers' => ['CC', 'g++'],
                 'libpath' => 'LD_LIBRARY_PATH',
                 'aceplatform' => 'sunos5_$COMP', # $COMP = sunc++ or g++
                 'aceconfig' => 'sunos$UNAMER', # $UNAMER = `uname -r`
                },
   'linux' => {
               'compilers' => ['g++', 'clang', 'clang++'],
               'libpath' => 'LD_LIBRARY_PATH',
               'aceplatform' => 'linux_$NONSTDCOMP', # $NONSTDCOMP = clang
              },
   'linux-cross' => {
                     'libpath' => 'LD_LIBRARY_PATH',
                     'aceplatform' => 'linux',
                     'aceconfig' => 'linux',
                     'no_host' => 1,
                     'java_platform' => 'linux',
                     'usage' => ['Use --target-compiler to specify the ' .
                                 'cross-compiler binary',
                                ],
                    },
   'freebsd' => {
                    'compilers' => ['clang++'],
                    'libpath' => 'LD_LIBRARY_PATH',
                },
   'lynxos-178' => {
                    'libpath' => 'LD_LIBRARY_PATH',
                    'no_host' => 1,
                    'aceplatform' => 'lynxos',
                    'compiler_root_env' => 'ENV_PREFIX',
                    'usage' => ['Set up the cross-compile using '.
                                'the script from LynxOS',
                               ],
                   },
   'vxworks' => {
                 'libpath' => 'LD_LIBRARY_PATH',
                 'no_host' => 1,
                 'usage' => ['Use the wrenv script before running configure',
                             'Specify the VSB with --macros=VSB_DIR=<dir>',
                            ],
                },
   'android' => {
                  'libpath' => 'LD_LIBRARY_PATH',
                  'aceplatform' => 'android',
                  'aceconfig' => 'android',
                  'no_host' => 1,
                  'usage' => [
                      "Use --macros=ANDROID_ABI=<ARCH> to specify the",
                      "target architecture.",
                      "Use --macros=android_sdk=<SDK_PATH> and",
                      "--macros=android_target_api=<API_NUMBER> to specify",
                      "where to find android.jar.",
                      "See docs/android.md for details.",
                    ],
                  'needs_i2jrt_corba' => 1,
                  'java_platform' => 'android',
                },
    'ios' => {
                'compilers' => ['clang++'],
                'libpath' => 'DYLD_LIBRARY_PATH',
                'no_host' => 1,
                'aceconfig' => 'macosx-iOS',
                'aceplatform' => 'macosx_iOS',
                'usage' => [
                    "Use --macros=IPHONE_TARGET=SIMULATOR or",
                    "--macros=IPHONE_TARGET=HARDWARE",
                    "to specify the target architecture."
                  ],
              }
  );

sub targetUsage {
  my $status = shift;
  print "Cross-compile targets: specify --target=TGT where TGT is one of:\n";
  for my $k (sort keys %platforminfo) {
    if ($platforminfo{$k}->{'no_host'}) {
      print "\t$k\n";
      if ($platforminfo{$k}->{'usage'}) {
        for my $line (@{$platforminfo{$k}->{'usage'}}) {
          print "${targetUsageIndent}$line\n";
        }
      }
    }
  }
  exit $status;
}

## arg processing and usage

my $argPadding = 29;
my $argIndent = "\n   " . (' ' x $argPadding);

my @specs = # Array of array-refs, each inner array is an option group which
            # has the format [Group Description, Opt1 Spec, Opt1 Description,
            # Opt2 Spec, Opt2 Description, ...]
# <default>OPT is a custom token marking a string option that
# defaults to $opts{'OPT'} = '' and can be overridden by opts by passing
# --no-OPT or --OPT with a value.
  (
   ['Options controlling the configure script:',
    'help|h|?', 'Show this help and exit',
    'target-help', 'Show details of cross-compile target configs',
    'verbose|v', 'Trace script execution',
    'dry-run|n', 'Don\'t do anything',
    'backup!', 'Make backup of build configuration files (yes)',
   ],
   ['Build platform and compiler:',
    'host=s', 'Host (auto detect: linux, win32, solaris, macosx)',
    'compiler=s', 'Compiler (auto detect / guess by searching PATH)',
    'std=s', 'C++ standard version (for compilers that use -std)',
    'target=s', 'Cross-compile target (none): see --target-help',
    'target-compiler=s', 'Compiler for target (if req\'d): see --target-help',
    'host-tools=s', 'DDS_ROOT of host tools for cross compile (build)',
    'host-ace=s', 'Define host ACE_ROOT (uses relative path from' .
      $argIndent . 'target DDS_ROOT to target ACE_ROOT)',
    'host-tools-only!', 'Just build the host tools (no)',
    'prefix=s', 'Installation prefix (none)',
    'install-origin-relative!', 'Install with RPATH relative to $ORIGIN (no)',
    'workspace=s', 'Custom MPC workspace file to copy and use' .
      $argIndent . '(Use a builtin one)',
   ],
   ['Build flags:',
    'debug!', 'Debugging (yes)',
    'optimize!', 'Optimization (no)',
    'inline!', 'Inlining (yes)',
    'static!', 'Static libraries (no)',
    'ipv6!', 'IPv6 support (no)',
    'sanitize=s@', 'Build with a sanitizer, can pass multiple times' .
      $argIndent . 'or one list separated by commas, combining asan' .
      $argIndent . "and tsan isn't recommended (no sanitizers)" .
      $argIndent . '  asan: Address Sanitizer, gcc/clang only' .
      $argIndent . '  tsan: Thread Sanitizer, gcc/clang only' .
      $argIndent . '  ubsan: Undefined Behavior Sanitizer, clang only',
   ],
   ['Required dependencies for OpenDDS:',
    'ace=s', 'ACE (use ACE_ROOT, ACE_wrappers, or download)',
    'tao=s', 'TAO (use TAO_ROOT, ACE_ROOT/TAO, or download)',
    'mpc=s', 'MPC (use MPC_ROOT, ACE_ROOT/MPC, or download)',
    'doc-group|doc_group!', 'Use the DOC Group release of TAO 2.5.x (no)',
    'doc-group3|doc_group3!', 'Use the DOC Group release of TAO 3.x (no)',
    'ace-github-latest!', 'Clone latest ACE/TAO/MPC from GitHub (no)',
    'force-ace-tao', 'Force configuration of ACE/TAO (no)',
   ],
   ['Advanced configuration:',
    'configh=s@', 'Extra text for config.h',
    'macros=s@', 'Extra text for platform_macros.GNU',
    'features=s@', 'Extra text for default.features',
    'mpcopts=s@', 'Extra command-line args for MPC',
   ],
   ['Optional dependencies for OpenDDS (disabled by default unless noted otherwise):',
    'java:s', 'Java development kit (use JAVA_HOME)',
    'jboss:s', 'JBoss application server (use JBOSS_HOME)',
    'ant:s', 'Ant for JBoss (use ANT_HOME or system pkg)',
    'wireshark:s',
      'Wireshark dev headers or source not built with' .
      $argIndent . 'CMake (use WIRESHARK_SRC or system pkg)' .
      $argIndent . 'Implies --glib',
    'wireshark-cmake|wireshark_cmake:s',
      'Wireshark source built with CMake, requires' .
      $argIndent . '--wireshark-build. Requires --wireshark-lib if' .
      $argIndent . 'guessing fails (use WIRESHARK_SRC)' .
      $argIndent . 'Implies --glib',
    'wireshark-build|wireshark_build=s', 'Wireshark CMake Build Location',
    'wireshark-lib|wireshark_lib=s',
      'Optional Wireshark CMake libraries location' .
      $argIndent . 'relative to wireshark-build (guesses)',
    'glib:s', 'GLib for Wireshark (use GLIB_ROOT or system pkg)',
    '<default>rapidjson:s',
      'RapidJSON for Wireshark dissector and JSON' .
      $argIndent . 'Sample Serialization (Enabled by default,' .
      $argIndent . 'use git submodle, RAPIDJSON_ROOT, or system pkg)',
    'qt:s', 'Qt5 (use QTDIR or system pkg)',
    'qt-include:s', 'Qt include dir (use QT5_INCDIR, QTDIR/include,' .
      $argIndent . 'or sysytem package)',
    'xerces3:s', 'Xerces-C++ 3 for QoS XML handling, DDS Security',
    'openssl:s', 'OpenSSL for DDS Security',
    'cmake:s', 'Path to cmake binary for compiling Google Test' .
      $argIndent . 'Framework. (Check Path and Check Normal' .
      $argIndent . 'Locations)',
    'gtest:s', 'Path to Google Test Framework, required for' .
      $argIndent . 'tests (uses GTEST_ROOT).' .
      $argIndent . 'If not built, will try to build using CMake.',
   ],
   ['Optional OpenDDS features:',
    'built-in-topics!', 'Built-in Topics (yes)',
    'content-subscription!', 'Content-Subscription Profile (yes)',
    'content-filtered-topic!', 'ContentFilteredTopic (CS Profile) (yes)',
    'multi-topic!', 'MultiTopic (CS Profile) (yes)',
    'query-condition!', 'QueryCondition (CS Profile) (yes)',
    'ownership-profile!', 'Ownership Profile (yes)',
    'ownership-kind-exclusive!', 'Exclusive Ownership (Ownership Profile) (yes)',
    'object-model-profile!', 'Object Model Profile (yes)',
    'persistence-profile!', 'Persistence Profile (yes)',
    'safety-profile:s', 'Safety Profile: base or extended (none)',
    'tests!', 'Build tests, examples, and performance tests (no)' .
      $argIndent . 'Requires --gtest if missing git submodule',
    'security!', 'DDS Security plugin (no) Implies --openssl and' .
      $argIndent . '--xerces3',
   ],
  );

sub iterate {
  my $callback = shift;
  for my $group (@specs) {
    for my $n (1 .. (scalar @{$group} / 2)) {
      my $opt = ${$group}[$n * 2 - 1];
      my $descr = ${$group}[$n * 2];
      $opt =~ /(\<\w+\>)?([\w-]+)/;
      my $optkey = (defined $1 ? $1 : "") . $2;
      &$callback(${$group}[0], $opt, $descr, $optkey, @_);
    }
  }
}

sub usage {
  my $status = shift;
  my $current;
  my $ver;
  open VER, 'dds/Version.h' or die "ERROR: can't open dds/Version.h, stopped";
  while (<VER>) {
    $ver = $1 if /#define OPENDDS_VERSION "([^"]+)"/;
  }
  close VER;
  print <<"EOT";
Welcome to OpenDDS version $ver

Options for this script are listed below, with the default behavior described
in parenthesis after the option description.
Boolean options can take the form "--opt" or "--no-opt", the more commonly
needed one (the one that changes the default behavior) is shown below.
Options that require arguments are shown as "--opt=VAL"  Options with optional
arguments are shown as "--opt[=VAL]".  Options that can be repeated with
cumulative effect are shown with a trailing "...".  Some third-party
optional dependencies can be automatically located if they are installed in the
expected locations (see entries below marked with "system pkg").  In those
cases, specify the option as --opt without an = to enable the corresponding
feature in OpenDDS and use the default installation location.
EOT
  iterate(sub {
            my ($group, $opt, $descr, $optkey) = @_;
            if (!defined $current || $group ne $current) {
              $current = $group;
              print "\n$group\n";
            }
            $optkey .= '=VAL' if $opt =~ /=s/;
            $optkey .= '[=VAL]' if $opt =~ /:s/;
            $optkey = "[no-]$optkey" if ($opt =~ /!$/ && $descr =~ / \(yes\)/);
            $optkey =~ s/^\<default\>(.*)$/\[no-\]$1/g;
            $optkey .= '...' if $opt =~ /s\@$/;
            my $pad = $argPadding - length $optkey;
            print "--$optkey" . ' ' x (($pad > 0) ? $pad : 0) . " $descr\n";
          }
         );
  exit $status;
}

my $defaulted = {};

sub parseArgs {
  my @getopts = ();
  my @default = ();
  iterate(sub {
            my ($group, $opt, $descr, $optkey) = @_;
            if ($opt =~ /^<default>(.*)$/) {
              push @getopts, $1;
              $optkey =~ /^<default>(.*)$/;
              push @getopts, "no-$1";
              push @default, $1;
            }
            else  {
              push @getopts, $opt;
            }
          }
         );

  if (! -r 'rules.dds.GNU') {
    print "ERROR: this script must be run from its own directory\n";
    exit 1;
  }

  my $opts = {};
  GetOptions($opts, @getopts) or usage(1);
  usage(0) if $opts->{'help'};
  targetUsage(0) if $opts->{'target-help'};

  if ($opts->{'verbose'}) {
    print "Options:\n";
    new Dumpvalue()->dumpValue($opts);
  }

  for my $opt (@default) {
    if ($opt eq 'rapidjson' && defined $opts->{'safety-profile'}) {
      print("Although it's a default, rapidjson is not compatible with Safety ".
            "Profile so it will not be enabled\n") if $opts->{'verbose'};
      next;
    }
    if (!exists $opts->{$opt} && !exists $opts->{"no-$opt"}) {
      $opts->{$opt} = '';
      $defaulted->{$opt} = 1;
      print("By default, --$opt is added to the options\n") if $opts->{'verbose'};
    }
  }

  return $opts;
}

my $cross_compile = 0;
my %opts = %{parseArgs()};

my $debug = exists $opts{'debug'} ? $opts{'debug'} : 1;
my $backup = exists $opts{'backup'} ? $opts{'backup'} : 1;
my $force_ace_tao = exists $opts{'force-ace-tao'} ? $opts{'force-ace-tao'} : 0;

$opts{'host'} = perlOS_to_host() unless $opts{'host'};

my $is_windows = $opts{'host'} eq 'win32';

my ($slash, $exeext) = $is_windows ? ('\\', '.exe') : ('/', '');

my %specific =
  ($is_windows ?
   ('ext' => 'cmd', 'pathsep' => ';', 'refpre' => '%',
    'refpost' => '%', 'comment' => '::') :
   ('ext' => 'sh', 'pathsep' => ':', 'refpre' => '${',
    'refpost' => '}', 'comment' => '#')
  );

sub which {
  my $file = shift;
  for my $p (File::Spec->path()) {
    next if $p eq '.';
    if (-x "$p/$file") {
      return "$p/$file";
    }
    elsif ($exeext ne '' && -x "$p/$file$exeext") {
      return "$p/$file$exeext";
    }
  }
  return undef;
}

sub addCurLibPathRef {
  my $buildEnvRef = shift;
  my $platform = $opts{$buildEnvRef->{'build'}};
  my $libpathname = $platforminfo{$platform}->{'libpath'};
  my $curLibPathRef = $specific{'refpre'} . $libpathname . $specific{'refpost'};
  $buildEnvRef->{$libpathname} = $curLibPathRef;
}

sub run_command {
  my $command = shift;
  return command_utils::run_command(
    $command,
    script_name => 'configure',
    dry_run => $opts{'dry-run'},
    verbose => $opts{'verbose'},
    @_,
  );
}

my $curpathRef = $specific{'refpre'} . 'PATH' . $specific{'refpost'};
my %hostEnv = ('build' => 'host', 'PATH' => $curpathRef);
my %targetEnv = ('build' => 'target', 'PATH' => $curpathRef);

my $would_download; # can dry-run assume directories that don't exist yet?

sub locate_mpc {
  my $ace_src = shift;
  if (defined $opts{'mpc'}) {
    setEnv('MPC_ROOT', $opts{'mpc'});
  }
  elsif (!$ENV{'MPC_ROOT'} && (-r $ace_src . '/MPC/MPC.ico'
                               || ($opts{'dry-run'} && $would_download))) {
    setEnv('MPC_ROOT', $ace_src . $slash . 'MPC');
  }
  elsif (!$ENV{'MPC_ROOT'}) {
    die "ERROR: Can't find MPC.  Please set MPC_ROOT or make sure MPC exists".
      "\n in the 'MPC' directory under ACE_ROOT ($ace_src), stopped";
  }
  else {
    $targetEnv{'MPC_ROOT'} = $ENV{'MPC_ROOT'};
  }
  $hostEnv{'MPC_ROOT'} = $targetEnv{'MPC_ROOT'};
}

if (!exists $platforminfo{$opts{'host'}} ||
    $platforminfo{$opts{'host'}}->{'no_host'}) {
  die "ERROR: unknown host $opts{'host'}, stopped";
}
print "host system is: $opts{'host'}\n" if $opts{'verbose'};


$opts{'target'} = $opts{'host'} unless $opts{'target'};

if (!exists $platforminfo{$opts{'target'}}) {
  die "ERROR: unknown target $opts{'target'}, stopped";
}

if (exists($opts{'workspace'})) {
  if (!-r $opts{'workspace'}) {
    die "ERROR: workspace file $opts{'workspace'} isn't a readable file, stopped";
  }
  $opts{'workspace'} = Cwd::realpath($opts{'workspace'});
}

if (exists($opts{'prefix'}) && !File::Spec->file_name_is_absolute($opts{'prefix'})) {
  die("ERROR: --prefix argument $opts{'prefix'} is not an absolute path, stopped");
}

# Set initial libpath
addCurLibPathRef(\%hostEnv);
addCurLibPathRef(\%targetEnv);


if (defined $opts{'safety-profile'} && $opts{'safety-profile'} eq '') {
  print "Defaulting safety profile to extended\n";
  $opts{'safety-profile'} = 'extended';
}

my $build_host_tools = 0;
if ($opts{'host'} ne $opts{'target'} || $opts{'safety-profile'}) {
  $build_host_tools = !$opts{'host-tools'};
  print "Cross-compile build " .
    ($build_host_tools ? 'including' : 'excluding') . " host tools\n"
    if $opts{'verbose'};
  $cross_compile = 1;
}
my $has_host_compiler = $build_host_tools || !$cross_compile;

if ($platforminfo{$opts{'target'}}->{'compiler_root_env'}) {
  # This target puts its cross-compiler in the PATH before the host compiler,
  # we will need to override (not append to) PATH for the host build.
  my $root_env = $platforminfo{$opts{'target'}}->{'compiler_root_env'};
  my $root_dir = $ENV{$root_env};
  my @oldpath = split /$specific{'pathsep'}/, $ENV{'PATH'};
  my $newpath = join($specific{'pathsep'}, grep {!/^$root_dir/} @oldpath);
  $hostEnv{'PATH'} =~
    s/\Q$specific{'refpre'}\EPATH\Q$specific{'refpost'}\E/$newpath/;
}

if (!exists $platforminfo{$opts{'target'}}->{'needs_i2jrt_corba'}) {
  $platforminfo{$opts{'target'}}->{'needs_i2jrt_corba'} = 0;
}

## compiler

if ($has_host_compiler) {
  if ($opts{'compiler'}) {
    my $standard = 0;
    for my $stdcomp (@{$platforminfo{$opts{'host'}}->{'compilers'}}) {
      $standard = 1 if $opts{'compiler'} eq $stdcomp;
    }
    $opts{'nonstdcompiler'} = 1 unless $standard;
  }
  else {
    print "Auto-detecting compiler\n" if $opts{'verbose'};
    for my $stdcomp (@{$platforminfo{$opts{'host'}}->{'compilers'}}) {
      my $path = which($stdcomp);
      if ($path) {
        print "Found $stdcomp at: $path\n" if $opts{'verbose'};
        $opts{'compiler'} = $stdcomp;
        last;
      }
    }
    if (!defined $opts{'compiler'}) {
      die "ERROR: Can't find a compiler, set PATH or run this script with the ".
        "--compiler option.\n" . ($is_windows ? "  For Microsoft Visual C++, ".
                                  "run this script from the Visual Studio ".
                                  "Command Prompt.\n" : '') . "Stopped";
    }
  }
  print "compiler is: $opts{'compiler'}\n" if $opts{'verbose'};

  if ($opts{'compiler'} =~ /cl(\.exe)?$/i) {
    my $savepath = $ENV{'PATH'};
    my $clpath = which($opts{'compiler'});
    if ($clpath) {
      $clpath =~ s/vc\\bin(\\(x86_)?amd64)?/common7\\ide/i;
      $ENV{'PATH'} .= ";$clpath";
    }

    # Have CL Tell Us Its Target Architecture and Version
    my ($tmp_fd, $tmp_filename) = File::Temp::tempfile();
    my $cl_check = << "EOF";
#ifdef _M_X64
#  define ARCH x64
#elif defined(_M_IX86)
#  define ARCH Win32
#elif defined(_M_ARM64)
#  define ARCH ARM64
#elif defined(_M_ARM)
#  define ARCH ARM
#else
#  define ARCH Unknown
#endif
CL is _MSC_VER ARCH
EOF
    print $tmp_fd $cl_check;
    close($tmp_fd);
    my $cl_command = "\"$opts{'compiler'}\" /EP $tmp_filename 2>&1";
    print "Running $cl_command\n" if $opts{'verbose'};
    open(my $cl_out_fd, "-|", $cl_command)
      or die "ERROR: Could not detect Visual C++ version, try running this ".
        "script from the Visual Studio Command Prompt.\nStopped";
    my $ver = 0;
    my $arch = '';
    while (my $line = <$cl_out_fd>) {
      chomp ($line);
      print "CL says: $line\n" if $opts{'verbose'} && $line !~ /^\s*$/;
      # Convert _MSC_VER to the form \d+\.\d
      if ($line =~ /^CL is (\d+)\d (\w+)$/) {
        $ver = int($1) / 10;
        $arch = $2;
        last;
      }
    }
    close($cl_out_fd);
    unlink($tmp_filename)
      or warn "Unable to delete temporary file $tmp_filename: $!";
    print "CL Version is $ver, architecture is $arch\n" if $opts{'verbose'};
    if (!$ver && !$arch) {
      die "cl version probe failed, invalid output from cl\nStopped";
    }
    if ($arch eq 'Unknown') {
      die "cl version probe failed, no known architecture macro was defined\nStopped";
    }
    if (!exists $platforminfo{'win32'}->{'cl_archs'}->{$arch}) {
      die "cl version probe failed, invalid architecture \"$arch\"\nStopped";
    }
    if (!$platforminfo{'win32'}->{'cl_archs'}->{$arch}) {
      die "ERROR: Windows for $arch isn't supported\nStopped";
    }
    if (!exists $platforminfo{'win32'}->{'cl_versions'}->{$ver}) {
      die "ERROR: Unknown or unsupported Visual C++ compiler version: $ver\n"
        . "Stopped";
    }

    $opts{'compiler_version'} = $platforminfo{'win32'}->{'cl_versions'}->{$ver};
    $opts{'compiler_target_architecture'} = $arch;

    if ($ver >= 19) {
      push(@{$opts{'features'}}, 'no_cxx11=0');
      print "Visual C++ has C++11 support\n" if $opts{'verbose'};
    }
    $ENV{'PATH'} = $savepath;
    print "Detected Visual C++ version: $opts{'compiler_version'}\n"
      if $opts{'verbose'};
  }
  elsif ($opts{'compiler'} =~ /g\+\+/) { # GCC or Clang
    my $version_string = `$opts{'compiler'} --version`;
    if ($opts{'std'}) {
      push(@{$opts{'macros'}}, 'CCFLAGS += -std=' . $opts{'std'});
      print "Added platform_macros for -std=$opts{std}\n" if $opts{'verbose'};
    }
    elsif ($opts{'compiler'} !~ /clang/) {
      if ($version_string =~ /\(.*\) (\d+)\.\d+/ && $1 >= 6) {
        $opts{'std'} = 'gnu++14';
        print "Detected GCC >= 6, default -std=gnu++14\n" if $opts{'verbose'};
      }
    }
    elsif ($opts{'compiler'} =~ /clang/ && $version_string !~ /^Apple / &&
           $version_string =~ /(\d+)\.(\d+)\.(\d+)/ && $1 > 6) {
      # Apple's version of Clang doesn't default to c++11 or higher, users
      # can pass --std= to change the C++ standard version used.
      # Other builds of Clang, if version 6 or newer, default to C++14 like GCC
      $opts{'std'} = 'gnu++14';
      print "Detected Clang >= 6, default -std=gnu++14\n" if $opts{'verbose'};
    }

    if ($opts{'std'} && $opts{'std'} =~ /(0x|11|1y|14|1z|17|2a|20|2b|23)$/) {
      push(@{$opts{'features'}}, 'no_cxx11=0');
      print "Compiler has >= C++11 support\n" if $opts{'verbose'};
    }
  }
}

sub looksRelative {
  my $val = shift;
  return substr($val, 0, 1) ne $slash && ($slash eq '/' || $val !~ /^[a-z]:/i);
}

sub normalizePath {
  my $val = shift;
  return Cwd::abs_path($val) if $val && -d $val && $val =~ /../;
  return $val;
}

sub setSomeEnv {
  my($hashref, $name, $val, $notdir) = @_;
  $val = Cwd::abs_path($val) if -d $val;
  if ($opts{'dry-run'} && !$notdir && looksRelative($val)) {
    $val = getcwd . $slash . $val;
  }
  $val =~ s!/!\\!g if $is_windows;
  $hashref->{$name} = $val;
}

sub setEnv {
  setSomeEnv(\%targetEnv, @_);
}

sub setHostEnv {
  setSomeEnv(\%hostEnv, @_);
}

## ace
my $ace_src;

if ($opts{'ace'}) {
  if ($opts{'ace'} ne 'download') {
    if (!-r $opts{'ace'} . '/ace/ACE.h') {
      die "ERROR: Can't find ACE at $opts{'ace'}.\nStopped";
    }
    $ace_src = $opts{'ace'};
  }
}
elsif ($ENV{'ACE_ROOT'}) {
  if (!-r $ENV{'ACE_ROOT'} . '/ace/ACE.h') {
    die "ERROR: Can't find ACE at $ENV{'ACE_ROOT'}.\nStopped";
  }
  $ace_src = $ENV{'ACE_ROOT'};
}
elsif (-r '../ACE_wrappers/ace/ACE.h') {
  die "ERROR: Older versions of this script would default to using ACE at " .
    "../ACE_wrappers, but this version doesn't.  Use the --ace command line " .
    "option to override this error.  Use --ace=download to have this script " .
    "download an ACE+TAO package and expand it to ACE_wrappers.\nStopped";
}
elsif (-r 'ACE_wrappers/ace/ACE.h') {
  $ace_src = 'ACE_wrappers';
}
elsif (-r 'ATCD/ACE/ace/ACE.h') {
  $ace_src = 'ATCD/ACE';
}
elsif (-r 'ACE_TAO/ACE/ace/ACE.h') {
  $ace_src = 'ACE_TAO/ACE';
}

$ace_src = normalizePath($ace_src);

## tao
my $tao_src;

if ($opts{'tao'}) {
  if (!-r $opts{'tao'} . '/tao/ORB.h') {
    die "ERROR: Can't find TAO at $opts{'tao'}.\nStopped";
  }
  $tao_src = $opts{'tao'};
}
elsif ($ENV{'TAO_ROOT'}) {
  if  (!-r $ENV{'TAO_ROOT'} . '/tao/ORB.h') {
    die "ERROR: Can't find TAO at $ENV{'TAO_ROOT'}.\nStopped";
  }
  $tao_src = $ENV{'TAO_ROOT'};
}
elsif (defined $ace_src && -r $ace_src . '/TAO/tao/ORB.h') {
  $tao_src = $ace_src . $slash . 'TAO';
}
elsif (defined $ace_src && -r $ace_src . '/../TAO/tao/ORB.h') {
  $tao_src = (File::Spec->splitpath($ace_src))[1] .'TAO';
}

$tao_src = normalizePath($tao_src);

if ($opts{'safety-profile'}) {
  # convert to lower case
  $opts{'safety-profile'} = lc($opts{'safety-profile'});
}

## Download ACE+TAO
if (!$ace_src || !$tao_src) {
  if ($opts{'ace-github-latest'}) {
    die "ERROR: Git not found in path (required to clone ACE/TAO/MPC)"
      if ! which('git');

    my $urlbase = 'https://github.com/DOCGroup';

    print "Cloning git repo: $urlbase/ACE_TAO\n";
    my $err = run_command(['git', 'clone', '-q', '--depth=1',
                           '-b', 'ace6tao2', "$urlbase/ACE_TAO"]);
    die "ERROR: Failed to clone ACE/TAO from GitHub\nStopped"
      if $err ||
        ! -r 'ACE_TAO/ACE/ace/ACE.h' ||
          ! -r 'ACE_TAO/TAO/tao/ORB.h';

    run_command(['git', '--no-pager', 'log', '-1', '--oneline'], chdir => 'ACE_TAO');

    print "Cloning git repo: $urlbase/MPC into ACE_TAO/ACE/MPC\n";
    $err = run_command(['git', 'clone', '-q', '--depth=1', "$urlbase/MPC", 'ACE_TAO/ACE/MPC']);
    die "ERROR: Failed to clone MPC (into ACE_TAO/ACE/MPC) from GitHub\nStopped"
      if $err ||
        ! -r 'ACE_TAO/ACE/MPC/mwc.pl';

    run_command(['git', '--no-pager', 'log', '-1', '--oneline'], chdir => 'ACE_TAO/ACE/MPC');

    $ace_src = normalizePath('ACE_TAO/ACE');
    $tao_src = normalizePath('ACE_TAO/TAO');
  }
  else {
    my($urlbase, $file, $download_message);

    if ($opts{'doc-group'}) {
      my $github_url_tao_version = $doc_tao2_version;
      $github_url_tao_version =~ s/\./_/g;
      $urlbase = 'https://github.com/DOCGroup/ACE_TAO/releases/download/ACE%2BTAO-' . $github_url_tao_version . '/';
      $file = 'ACE+TAO-src-' . $doc_tao2_version . '.'
        . ($is_windows ? 'zip' : 'tar.gz');
      $download_message = "Downloading $file";
    }
    elsif ($opts{'doc-group3'}) {
      my $github_url_tao_version = $doc_tao3_version;
      $github_url_tao_version =~ s/\./_/g;
      $urlbase = 'https://github.com/DOCGroup/ACE_TAO/releases/download/ACE%2BTAO-' . $github_url_tao_version . '/';
      $file = 'ACE+TAO-src-' . $doc_tao3_version . '.'
        . ($is_windows ? 'zip' : 'tar.gz');
      $download_message = "Downloading $file";
    }
    else {
      $urlbase = 'http://download.ociweb.com/TAO-' . $oci_tao_version . '/';
      $file = 'ACE+TAO-' . $oci_tao_version . '_with_latest_patches_NO_makefiles.'
        . ($is_windows ? 'zip' : 'tar.gz');
      $download_message =
        "Downloading ACE+TAO $oci_tao_version with latest patches";
    }
    if (-r $file) {
      print "Using ACE+TAO source package $file\n" if $opts{'verbose'};
    }
    else {
      $would_download = 1;
      eval {
        require LWP::UserAgent;
        my $ua = LWP::UserAgent->new;
        $ua->env_proxy;
        print $download_message . "\n";
        if ($opts{'dry-run'}) {
          print "Dry-run: would LWP::UserAgent get $urlbase$file\n";
        }
        else {
          my $response = $ua->get($urlbase . $file, ':content_file' => $file);
          if ($response->is_error) {
            die $response->message . "\nstopped";
          }
        }
      };
      if ($@) {
        if (which('wget')) {
          print $download_message . " (using wget)\n";
          if (run_command(['wget', "$urlbase$file"])) {
            die "ERROR from wget, stopped";
          }
        }
        elsif (which('curl')) {
          print $download_message . " (using curl)\n";
          if (run_command(['curl', '-L', "$urlbase$file", '-o', "$file"])) {
            die "ERROR: from curl, stopped";
          }
        }
        else {
          die "ERROR: Can't download ACE+TAO using LWP, wget, or curl.\n" .
            "Download ACE+TAO from $urlbase$file, place the file here\n, " .
            "and re-run the script.\nStopped";
        }
      }
    }

    print "Extracting archive $file\n";
    $ENV{'ACTIVEPERL_CONFIG_DISABLE'} = 1 if $^O eq 'MSWin32';
    $ENV{'ACTIVEPERL_CONFIG_SILENT'} = 1 if $^O eq 'MSWin32';
    eval {require Archive::Extract;};
    if ($@) {
      my $err = 1;
      my $ddsroot = getcwd;
      if (!$is_windows) {
        $err = run_command([($^O eq 'solaris' ? 'g' : '') . 'tar', 'xzf', "$ddsroot/$file"]);
      }
      else {
        # Try Archive::Zip
        print "Archive::Extract isn't installed, trying Archive::Zip\n" if $opts{'verbose'};
        eval {require Archive::Zip};
        if ($@) {
          print "Neither Archive::Extract or Archive::Zip are installed\n" if $opts{'verbose'};
        }
        else {
          if ($opts{'dry-run'}) {
            print "Dry-run: would Archive::Zip $file\n";
          }
          else {
            my $zip = Archive::Zip->new();
            if ($zip->read( $file ) == Archive::Zip::AZ_OK() &&
              $zip->extractTree() == Archive::Zip::AZ_OK()) {
              $err = 0;
            }
          }
        }
      }

      if ($err) {
        die "ERROR: Can't extract $file, extract it to " . Cwd::abs_path('.') .
          "\nand run this script again.\nStopped";
      }
    }
    else {
      if ($opts{'dry-run'}) {
        print "Dry-run: would Archive::Extract $file\n";
      }
      else {
        if ($^O ne 'MSWin32') {
          no warnings 'once';
          $Archive::Extract::PREFER_BIN = 1;
        }
        my $ae = Archive::Extract->new('archive' => $file);
        if (!$ae->extract('to' => '.')) {
          die $ae->error . "\nstopped";
        }
      }
    }

    unlink $file;
    print "Removed $file\n" if $opts{'verbose'};
    $ace_src = 'ACE_wrappers';
    $tao_src = 'ACE_wrappers/TAO';
  }
}

print "Using ace_src: $ace_src\n" if $opts{'verbose'};
print "Using tao_src: $tao_src\n" if $opts{'verbose'};

sub clone_host_and_target {
  my $source_dir = shift;
  locate_mpc($ace_src);
  print "cloning build tree\n" if $opts{'verbose'};
  if (run_command(
      ["$targetEnv{'MPC_ROOT'}/clone_build_tree.pl", 'host', 'target'],
      chdir => $source_dir)) {
    die("Failed to clone tree");
  }
}

sub backup {
  my $file = shift;
  if (!$opts{'dry-run'} && -r $file) {
    print "WARNING: overwriting existing $file\n";
    if ($backup) {
      my $backup_path = $file . '.bak';
      $backup_path .= ".$backup_timestamp" if -e $backup_path;
      copy($file, $backup_path);
      print "  (saved a backup copy as $backup_path)\n";
    }
    unlink $file;
  }
}

sub backup_and_copy {
  my $src = shift;
  my $dst = shift;
  backup($dst);
  if ($opts{'dry-run'}) {
    print("Would copy $src to $dst\n");
  }
  else {
    copy($src, $dst);
  }
}

sub backup_and_open {
  my $file = shift;
  backup($file);
  if ($opts{'dry-run'}) {
    return File::Temp->new();
  }
  my $fh = new FileHandle;
  open $fh, ">$file" or die "ERROR: Can't write to $file, stopped";
  return $fh;
}

sub dump_and_unlink { # removes temp files created by dry-run
  my $tfile = shift;
  if ($opts{'verbose'}) {
    open TMP, $tfile;
    print <TMP>;
    close TMP;
  }
  unlink $tfile;
}

sub write_config_h {
  my %buildEnv = %{shift()};
  my $platform = $opts{$buildEnv{'build'}};
  my $pi = $platforminfo{$platform};
  $opts{'optimize'} = 0 if !exists $opts{'optimize'};

  my $CFGH = backup_and_open("$buildEnv{'ACE_ROOT'}/ace/config.h");
  if ($buildEnv{'build'} eq 'target') {
    for my $line (@{$opts{'configh'}}) {
      print $CFGH "$line\n";
    }
  }
  my $cfg = $platform;
  if ($pi->{'aceconfig'}) {
    $cfg = $pi->{'aceconfig'};
    $cfg =~ s/\$UNAMER/my $u = `uname -r`; chomp $u; $u/e;
  }
  $cfg .= '-' . $opts{'host_version'} if $opts{'host_version'};
  print $CFGH "#include \"ace/config-$cfg.h\"\n";
  if (defined $opts{'no-opendds-safety-profile'}) {
    print $CFGH "#define ACE_FACE_SAFETY_" . uc($opts{'safety-profile'}) . "\n";
    if ($opts{'safety-profile'} eq 'extended') {
      print $CFGH "#ifndef ACE_HAS_ALLOC_HOOKS\n";
      print $CFGH "#  define ACE_HAS_ALLOC_HOOKS\n";
      print $CFGH "#endif\n";
    }
  }

  close $CFGH;
  print "Wrote $buildEnv{'ACE_ROOT'}/ace/config.h\n" if $opts{'verbose'};
  dump_and_unlink($CFGH) if $opts{'dry-run'};
}

my $wrote_df = 0;

sub write_default_features {
  my %buildEnv = %{shift()};
  my @feat;
  if ($buildEnv{'build'} eq 'target') {
    push(@feat, 'ipv6=1') if $opts{'ipv6'};
    push(@feat, @{$opts{'features'}}) if $opts{'features'};
  }
  elsif ($opts{'java'}) {
    push(@feat, 'java=1');
  }

  if (@feat) {
    my $DF = backup_and_open("$buildEnv{'ACE_ROOT'}/bin/MakeProjectCreator" .
                             "/config/default.features");
    $wrote_df = 1;
    for my $f (@feat) {
      print $DF "$f\n";
    }
    $DF->close;
    print "Wrote $buildEnv{'ACE_ROOT'}/.../default.features\n"
      if $opts{'verbose'};
    dump_and_unlink($DF) if $opts{'dry-run'};
  }
}

my %all_sanitizers = (
  asan => {
    fsanitize => 'address',
    env => {
      LSAN_OPTIONS => "suppressions=$FindBin::RealBin/etc/asan-suppr.txt",
      ASAN_OPTIONS => 'detect_leaks=1:fast_unwind_on_malloc=0:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1',
    },
    compiler_args => ['-fsanitize-address-use-after-scope'],
  },
  tsan => {
    fsanitize => 'thread',
    env => {
      TSAN_OPTIONS => "history_size=7 suppressions=$FindBin::RealBin/etc/tsan-suppr.txt",
    },
  },
  ubsan => {
    fsanitize => 'undefined',
    env => {
      UBSAN_OPTIONS => "suppressions=$FindBin::RealBin/etc/ubsan-suppr.txt:print_stacktrace=1",
    },
    configh => ['#define ACE_INITILAIZE_MEMORY_BEFORE_USE'],
    compiler_args => ['-fsanitize-coverage=trace-pc-guard'],
  },
);
my %enabled_sanitizers = ();
for my $arg (@{$opts{sanitize}}) {
  for my $name (split(/,/, $arg)) {
    if (exists($all_sanitizers{$name})) {
      $enabled_sanitizers{$name} = $all_sanitizers{$name};
    }
    else {
      die("ERROR: \"$name\" isn't a valid sanitizer to pass to --sanitize\nStopped");
    }
  }
}

my @fsanitize = ();
my @sanitizer_compiler_args = ();
my @sanitizer_linker_args = ();
for my $name (sort(keys(%enabled_sanitizers))) {
  my $sanitizer = $enabled_sanitizers{$name};
  push(@fsanitize, $sanitizer->{fsanitize});
  if (exists($sanitizer->{env})) {
    for my $env (keys(%{$sanitizer->{env}})) {
      setEnv($env, $sanitizer->{env}->{$env});
    }
  }
  if (exists($sanitizer->{configh})) {
    push(@{$opts{'configh'}}, @{$sanitizer->{configh}});
  }
  if (exists($sanitizer->{compiler_args})) {
    push(@sanitizer_compiler_args, @{$sanitizer->{compiler_args}});
  }
  if (exists($sanitizer->{linker_args})) {
    push(@sanitizer_linker_args, @{$sanitizer->{linker_args}});
  }
}
if (scalar(@fsanitize)) {
  if (!$debug) {
    die("ERROR: Using sanitizers requires --debug");
  }
  my @common = ('-ggdb', '-fsanitize=' . join(',', @fsanitize));
  @sanitizer_compiler_args = ('-O1', @common, '-fno-omit-frame-pointer', @sanitizer_compiler_args);
  @sanitizer_linker_args = (@common, @sanitizer_linker_args);
}

my @ace_macros = ('debug',
                  'optimize',
                  'inline',
                  'static',
                  'ipv6');

my @platformmacros;
sub write_platform_macros {
  my %buildEnv = %{shift()};
  if (!$is_windows ||
      ($cross_compile && $buildEnv{'build'} eq 'target')) {
    my $pi = $platforminfo{$opts{$buildEnv{'build'}}};
    my $PMG = backup_and_open("$buildEnv{'ACE_ROOT'}/include/makeinclude" .
                               "/platform_macros.GNU");
    my $macro_cross_compile = 0;
    if ($buildEnv{'build'} eq 'target') {
      for my $line (@{$opts{'macros'}}) {
        print $PMG "$line\n";
      }
      for my $key (@ace_macros) {
        if (exists $opts{$key}) {
          my $macro = ($key eq 'static') ? 'static_libs_only' : $key;
          print $PMG "$macro = $opts{$key}\n";
        }
      }
      if ($cross_compile) {
        print $PMG 'TAO_IDL = $(HOST_ACE)/bin/tao_idl', "\n";
        if ($is_windows) {
          print $PMG "HOST_EXE_EXT = .exe\n";
        }
        print $PMG 'TAO_IDL_DEP = $(TAO_IDL)$(HOST_EXE_EXT)', "\n";
        if ($has_host_compiler) {
          print $PMG 'TAO_IDL_PREPROCESSOR = ', $opts{'compiler'}, "\n";
        }
        print $PMG 'TAO_IDLFLAGS += -g $(HOST_ACE)/bin/ace_gperf', "\n";
        print $PMG 'build_tao_idl_be = 0', "\n";
        unless ($build_host_tools) {
          my $host_ace;
          if (exists $opts{'host-ace'}) {
            $host_ace = $opts{'host-ace'};
          }
          else {
            $host_ace = '$(HOST_DDS)' . nested($ace_src, '.');
          }
          print $PMG "HOST_ACE = $host_ace\n";
        }
        if ($opts{'target-compiler'}) {
          my $tcomp = $opts{'target-compiler'};
          if ($tcomp =~ s/-g([c+])\1$/-/) {
            $macro_cross_compile = 1;
            print $PMG 'CROSS_COMPILE = ', $tcomp, "\n";
          }
          else {
            $opts{'nonstdcompiler'} = $tcomp;
            print $PMG 'LDFLAGS += -Wl,-rpath-link,$(ACE_ROOT)/lib', "\n";
          }
        }
        if ($opts{'target'} eq 'android') {
          print $PMG 'ifeq (,$(findstring -isystem$(ACE_ROOT),$(INCLDIRS)))', "\n";
          print $PMG '  INCLDIRS += -isystem $(ACE_ROOT)', "\n";
          print $PMG 'endif', "\n";
        }
      }
    }
    for my $f (@platformmacros) {
      print $PMG ($f =~ /=/ ? $f : "$f=1"), "\n";
    }
    if ($buildEnv{'build'} eq 'host') {
      print $PMG "static_libs_only = 1\n";
      print $PMG "java = 1\n" if $opts{'java'};
    }
    if ($opts{'prefix'}) {
      print $PMG "INSTALL_PREFIX=" . $opts{'prefix'} . "\n";
    }
    if (scalar(@fsanitize)) {
      print $PMG
        'CPPFLAGS += ' . join(' ', @sanitizer_compiler_args) . "\n" .
        'LDFLAGS += ' . join(' ', @sanitizer_linker_args) . "\n";
    }
    my $plat = $opts{$buildEnv{'build'}};
    if ($pi->{'aceplatform'}) {
      $plat = $pi->{'aceplatform'};
      $plat =~ s/\$COMP/($opts{'compiler'} =~ m!CC!) ? 'sunc++' : 'g++'/e;
      $plat =~ s/\$NONSTDCOMP/($opts{'compiler'} =~ m!clang!) ? 'clang' : ''/e;
      $plat =~ s/_$//;
    }
    $plat .= '_' . $opts{'host_version'} if $opts{'host_version'};
    print $PMG "include \$(ACE_ROOT)/include/makeinclude/platform_$plat.GNU\n";
    if ($opts{'nonstdcompiler'} && !$macro_cross_compile) {
      for my $var ('CC', 'CXX', 'LD') {
        print $PMG "$var = $opts{'compiler'}\n";
      }
    }
    if ($opts{'prefix'} && $opts{'install-origin-relative'}) {
      print $PMG <<'EOT';
install_rpath = 0
INSTALL_ORIGIN = $$ORIGIN/../$(INSTALL_LIB)
LDFLAGS += '-Wl,-rpath,$(INSTALL_ORIGIN)' $(LD_RPATH_FLAGS)
EOT
    }
    $PMG->close;
    print "Wrote $buildEnv{'ACE_ROOT'}/.../platform_macros.GNU\n"
      if $opts{'verbose'};
    dump_and_unlink($PMG) if $opts{'dry-run'};
  }
}

## Optional OpenDDS dependencies
my %optdep = (
  'java' => {env => 'JAVA_HOME', sanity => 'include/jni.h', mpc => 'java'},
  'jboss' => {env => 'JBOSS_HOME', sanity => 'lib/jboss-common.jar'},
  'ant' => {env => 'ANT_HOME', sanity => 'bin/ant'},
  'wireshark' => {env => 'WIRESHARK_SRC', sanity => 'epan/packet.h', mpc => 'wireshark'},
  'wireshark-cmake' => {
    env => 'WIRESHARK_SRC',
    sanity => 'epan/packet.h',
    mpc => 'wireshark_cmake',
  },
  'wireshark-build' => {env => 'WIRESHARK_BUILD', sanity => 'config.h'},
  'wireshark-lib' => {env => 'WIRESHARK_LIB', may_be_blank => 1},
  'glib' => {
    env => 'GLIB_ROOT',
    sanity => {
      'include/glib-2.0/glib.h' => undef,
      'include/glib.h' => 'glib_versioned_includes=0',
    },
  },
  'rapidjson' => {
    env => 'RAPIDJSON_ROOT',
    sanity => 'include/rapidjson/rapidjson.h',
    mpc => 'no_rapidjson=0',
  },
  'qt' => {env => 'QTDIR', sanity => '', mpc => 'qt5'},
  'xerces3' => {
    env => 'XERCESCROOT',
    sanity => 'include/xercesc/dom/DOM.hpp',
    mpc => 'xerces3',
  },
  'openssl' => {env => 'SSL_ROOT', sanity => 'include/openssl/opensslv.h', mpc => 'ssl'},
);

my $host_tools_only = exists $opts{'host-tools-only'} && $opts{'host-tools-only'};
if ($host_tools_only) {
  print "--host-tools-only implies --static\n" if $opts{'verbose'};
  $opts{'static'} = 1;
  if ($cross_compile) {
    die "ERROR: Can't use --host-tools-only for cross compile\nStopped";
  }
}

if ((exists $opts{'wireshark'} || exists $opts{'wireshark-cmake'}) &&
    !exists $opts{'glib'}) {
  print "--wireshark and --wireshark-cmake imply --glib\n" if $opts{'verbose'};
  $opts{'glib'} = '';
}

# Use this to check if tests are enabled (instead of $opts{'tests'} directly).
my $tests = exists $opts{'tests'} && $opts{'tests'};

my @ace_features = ('xerces3');

if (exists $opts{'java'}) {
  if ($opts{'static'} && !$host_tools_only) {
    die "ERROR: --static can't be used with --java\nStopped";
  }
  my $host_java_platform = perlOS_to_java_platform();
  if ($cross_compile) {
    if (exists $platforminfo{$opts{'target'}}->{'java_platform'}) {
      setEnv('JAVA_PLATFORM', $platforminfo{$opts{'target'}}->{'java_platform'});
      setHostEnv('JAVA_PLATFORM', $host_java_platform);
    }
    else {
      die "Cross-compile with Java for $opts{'target'} in not supported.\nStopped";
    }
  }
  else {
    setEnv('JAVA_PLATFORM', $host_java_platform);
  }
}

my $try_to_use_qt_system_pkg =
  exists $opts{'qt'} && !length($opts{'qt'}) && !exists $opts{'qt-include'};

# Default to Wireshark Development Package if installed and a path wasn't
# supplied.
my $wireshark_install = '/usr/include/wireshark';
if (exists $opts{'wireshark'} && !defined $ENV{'WIRESHARK_SRC'} && $opts{'wireshark'} eq '') {
  my $sanity = $optdep{'wireshark'}->{sanity};
  if (-f File::Spec->catdir($wireshark_install, $sanity)) {
    $opts{'wireshark'} = $wireshark_install;
  }
  else {
    die "ERROR: --wireshark must be given a value because there is not a " .
        "development package installed at " . $wireshark_install . ", stopped";
  }
}

my $wireshark_lib_defaulted = 0;
if (exists $opts{'wireshark-cmake'} && !exists $opts{'wireshark-lib'}) {
  if ($^O =~ /MSWin32/) {
    $opts{'wireshark-lib'} = "run\\RelWithDebInfo";
  }
  elsif ($^O =~ /darwin/) {
    $opts{'wireshark-lib'} = "run/Wireshark.app/Contents/Frameworks";
  }
  elsif ($^O =~ /linux/) {
    $opts{'wireshark-lib'} = "";
  }
  else {
    die "ERROR: --wireshark-lib is needed because we couldn't decide on a default value";
  }
  $wireshark_lib_defaulted = 1;
}

if ($opts{'security'}) {
  unless (exists $opts{'openssl'}) {
    $opts{'openssl'} = '';
    print "Forcing --openssl (security dependency)\n" if $opts{'verbose'};
  }
  unless (exists $opts{'xerces3'}) {
    $opts{'xerces3'} = '';
    print "Forcing --xerces3 (security dependency)\n" if $opts{'verbose'};
  }
}

# Try to find CMake
unless ($opts{'cmake'}) {
  print "--cmake not specified by user; searching path...\n" if $opts{'verbose'};
  $opts{'cmake'} = which('cmake');

  unless ($opts{'cmake'}) {
    print "CMake not found in path; searching default location...\n"
      if $opts{'verbose'};

    $opts{'cmake'} =
      ($is_windows ?
        (-f $ENV{'ProgramFiles'} . '\\CMake\\bin\\cmake.exe' ?
            $ENV{'ProgramFiles'} . '\\CMake\\bin\\cmake.exe' :
            $ENV{'ProgramFiles(x86)'} . '\\CMake\\bin\\cmake.exe') :
        '/usr/bin/cmake');
  }
}
my $has_cmake = -f $opts{'cmake'};
if ($has_cmake) {
  print "Using CMake '$opts{'cmake'}' if needed\n" if $opts{'verbose'};
}
else {
  print "Could not find CMake at '$opts{'cmake'}'\n" if $opts{'verbose'};
}

sub targets_win64 {
  my $arch = exists $opts{'compiler_target_architecture'} ?
               $opts{'compiler_target_architecture'} : '';

  return $arch eq 'x64';
}

sub system_default_install_dir {
  if ($is_windows) {
    # When the host is 32-bit.
    return $ENV{'ProgramFiles'}
      if ! exists $ENV{'ProgramFiles(x86)'};

    return targets_win64() ? $ENV{'ProgramFiles'} : $ENV{'ProgramFiles(x86)'};
  }
  return '/usr';
}

my $build_gtest = 0;
if (exists $opts{'gtest'} || $tests) {
  my $gtest_sanity = 'include/gtest/gtest.h';
  my $gtest_root = "";

  if ($opts{'gtest'}) {
    die "ERROR: '$gtest_sanity' not found in supplied gtest directory '$opts{'gtest'}'"
      if ! -f File::Spec->catfile($opts{'gtest'}, $gtest_sanity);

    $gtest_root = $opts{'gtest'};
    setEnv('GTEST_ROOT', $gtest_root);

  }
  else {
    my $sm_dir = Cwd::abs_path('./tests/googletest');
    my $sm_src = File::Spec->catdir($sm_dir, 'googletest');
    my $sys_dir = File::Spec->catdir((system_default_install_dir(),
                              $is_windows ? 'googletest-distribution' : ''));

    if (-f File::Spec->catfile($sm_src, $gtest_sanity)) {
      # Check for existing build
      $gtest_root = File::Spec->catdir($sm_dir, 'build', 'install');
      $build_gtest = ! -d $gtest_root;
      $opts{'gtest'} = $sm_dir;
      # No need to set env. variable with submodule UNLESS we're in safetyprofile
      if ($opts{'safety-profile'}) {
        print "Setting GTEST_ROOT to ${gtest_root} for use in safety-profile.\n" if $opts{'verbose'};
        setEnv('GTEST_ROOT', $gtest_root);
      }
    }
    elsif (-f File::Spec->catfile($sys_dir, $gtest_sanity)) {
      $opts{'gtest'} = $sys_dir;
      $gtest_root = $sys_dir;
      setEnv('GTEST_ROOT', $gtest_root);
    }
    else {
      die "ERROR: Google Test '$gtest_sanity' not found in submodule src '$sm_src' or " .
        "default install dir '$sys_dir', please pass a correct version of Google Test to --gtest\nStopped";
    }
  }
  if ($is_windows && -d File::Spec->catdir($gtest_root, 'bin')) {
    push_path(\%targetEnv, $gtest_root . $slash . 'bin');
  }
  elsif (!$is_windows && -f File::Spec->catfile($gtest_root, 'lib', 'libgtest.so')) {
    push_libpath(\%targetEnv, $gtest_root . $slash . 'lib');
  }

}
if ($build_gtest && !$has_cmake) {
  die "ERROR: Google Test in $opts{'gtest'} must be built but can't find CMake\nStopped";
}

if (exists $opts{'rapidjson'}) {
  if ($opts{'rapidjson'} eq '') {
    my $sanity = $optdep{'rapidjson'}->{sanity};
    my $sm_dir = Cwd::abs_path('./tools/rapidjson');
    my $sys_dir = File::Spec->catdir((system_default_install_dir(),
                              $is_windows ? 'RapidJSON' : ''));

    my $rapidjson_msg =
      "Could not find RapidJSON (using '$sanity') in:\n".
      "    Git Submodule: '$sm_dir'\n" .
      "    Default Install Prefix: '$sys_dir'\n";
    if (-f File::Spec->catfile($sm_dir, $sanity)) {
      $opts{'rapidjson'} = $sm_dir;
    }
    elsif (-f File::Spec->catfile($sys_dir, $sanity)) {
      $opts{'rapidjson'} = $sys_dir;
    }
    elsif (exists $defaulted->{'rapidjson'}) {
      print "${rapidjson_msg}Continuing without it.\n" if $opts{'verbose'};
      delete $opts{'rapidjson'};
    }
    else {
      die "ERROR: ${rapidjson_msg}Stopped";
    }
    if ($opts{'verbose'} && exists $opts{'rapidjson'}) {
      print "Using '$opts{'rapidjson'}' for RapidJSON\n";
    }
  }
}

my %use_system_pkg = map {$_, 1} qw/ant glib qt xerces3 openssl/;

my %use_win_default = (
    'openssl' => system_default_install_dir() . '/OpenSSL',
    'xerces3' => system_default_install_dir() . '/xerces-c');

my %need_platform_macros = map {$_, 1} qw/xerces3/;

sub default_java_home {
  my $host = $opts{'host'};
  my $result = '';
  if ($host eq 'macosx') {
    $result = `/usr/libexec/java_home`;
    chomp $result;
    return $result;
  }
  else {
    $result = which('javac');
    if (!$result) {
      die "ERROR: No javac in PATH.\nStopped";
    }
    while (-l $result) {
      $result = readlink($result);
    }
    # remove bin/javac
    return dirname(dirname($result))
  }
}

# Detect and populate dependency info from environment variables
# or system-wide default paths.
for my $key (keys %optdep) {
  if (exists $opts{$key} && $opts{$key} eq '') {
    if (defined $optdep{$key}->{env} && $ENV{$optdep{$key}->{env}}) {
      $opts{$key} = $ENV{$optdep{$key}->{env}};

      if ($key eq 'java') {
        ## when the environment variable JAVA_HOME is set to a JRE location,
        ## try to resolve JAVA_HOME based on the location of javac
        my $java_home = default_java_home();
        if (!-r "$opts{$key}$slash$optdep{'java'}->{sanity}" and $java_home ne '') {
          $opts{'java'} = $java_home;
        }
      }
    }
    elsif (!$is_windows && exists $use_system_pkg{$key}) {
      $opts{$key} = '/usr';
      print "Defaulting $key to $opts{$key}\n" if $opts{'verbose'};
    }
    elsif ($is_windows && exists $use_win_default{$key}) {
      my $dir = $use_win_default{$key};

      if (-d $dir) {
        $opts{$key} = $dir;
      }
      else {
        die "Default '$key' directory '$dir' not found.\nStopped";
      }

      print "Defaulting $key to $opts{$key}\n" if $opts{'verbose'};
    }
    elsif ($key eq 'java') {

      $opts{'java'} = default_java_home();

      if ($opts{'java'} eq '') {
          die "ERROR: --$key requires a value.\nStopped";
      }
    }
    elsif (exists $optdep{$key}->{env} && !$optdep{$key}->{may_be_blank}) {
      die "ERROR: --$key requires a value.\nStopped";
    }
  }
}

sub has_feature {
  my $feat = shift;
  if ($opts{'features'}) {
    for my $f (@{$opts{'features'}}) {
      my ($key, $value) = split(/=/, $f);
      if ($key eq $feat) {
        return 1;
      }
    }
  }
  return 0;
}

my @features;
if ($opts{'java'}) {
  my $feat = 'java_pre_jpms';
  unless (has_feature($feat)) {
    my $javac = File::Spec->catfile($opts{'java'}, 'bin', 'javac');
    open(my $vers, "\"$javac\" -version |");
    while (<$vers>) {
      if (/javac (\d+)/) {
        print "Found javac major version $1\n" if $opts{'verbose'};
        if ($1 > 1) {
          push(@features, "$feat=0");
          print "Feature $feat=0 due to Java version\n" if $opts{'verbose'};
        }
        last;
      }
    }
  }
}

sub env_from_opt {
  my $key = shift;
  my $e = shift;
  my $notdir = shift;
  if ($opts{$key}) {
    setEnv($e, $opts{$key}, $notdir);
  }
}

# Enable MPC features and populate expected environment variables
# for all required "optional" dependencies.
for my $key (keys %optdep) {
  if (exists $opts{$key}) {
    print "Enabling $key\n" if $opts{'verbose'};
    my $e = $optdep{$key}->{env};
    my $s = $optdep{$key}->{sanity};
    my $m = $optdep{$key}->{mpc};
    if ($opts{$key} ne 'skip_version_check') {
      env_from_opt($key, $e) if defined $e;
      if (ref $s eq 'HASH') {
        my $ok = 0;
        for my $alt (keys %{$s}) {
          if (-r $targetEnv{$e} . '/' . $alt) {
            print "Found $key at $targetEnv{$e} using alternative $alt\n"
              if $opts{'verbose'};
            $ok = 1;
            push(@features, $s->{$alt}) if defined $s->{$alt};
            last;
          }
          print "Didn't find $key at $targetEnv{$e} using alternative $alt\n"
            if $opts{'verbose'};
        }
        unless ($ok) {
          die "ERROR: Can't find $key at $targetEnv{$e}.\nStopped";
        }
      }
      elsif ($s && !-r $targetEnv{$e} . '/' . $s) {
        die "ERROR: Can't find $key at $targetEnv{$e} (using $s).\nStopped";
      }
    }
    push(@features, $m) if $m;
    push(@platformmacros, $m) if $need_platform_macros{$key};
  }
}

if ($is_windows && !$opts{'optimize'}) { # look for nonstandard vcpkg layout
  for my $key ('xerces3', 'openssl') {
    my $env = $optdep{$key}->{env};
    if ($opts{$key} && $targetEnv{$env}) {
      if (-d $targetEnv{$env} . '/debug/lib') {
        print "Using debug/lib subdir for $key\n" if $opts{'verbose'};
        my $libenv = $env;
        $libenv =~ s/_?ROOT$/_LIBDIR/;
        setEnv($libenv, $targetEnv{$env} . '/debug/lib');
        $opts{$key . '-debugbin'} = 1;
      }
    }
  }
}

## OpenSSL version-detection / feature-injection
if ($opts{'openssl'} && $opts{'openssl'} ne 'skip_version_check') {
  my $ssl_version_file = $opts{'openssl'} . '/' . $optdep{'openssl'}->{sanity};

  open my $fh, '<', $ssl_version_file
    or die "ERROR: Failed to open '$ssl_version_file' for OpenSSL version detection.\nStopped";

  while (<$fh>) {
    if (/^\s*#\s*define\s*OPENSSL_VERSION_TEXT\s*"([^"]*)"/) {
      my $ver = $1;
      print "OpenSSL version detected: \"$ver\"\n" if $opts{'verbose'};
      if ($ver =~ /OpenSSL[\W]+1\.1/ || $ver =~ /OpenSSL[\W]+3\./) {
        print "OpenSSL 1.1+ MPC feature enabled\n" if $opts{'verbose'};
        push(@features, 'openssl11=1');
      }
      last;
    }
  }
}

sub win32_cmake_generator {
  my $ver = shift;

  my %map = ('vc9' => 'Visual Studio 9 2008',
             'vc10' => 'Visual Studio 10 2010',
             'vc11' => 'Visual Studio 11 2012',
             'vc12' => 'Visual Studio 12 2013',
             'vc14' => 'Visual Studio 14 2015',
             'vs2017' => 'Visual Studio 15 2017',
             'vs2019' => 'Visual Studio 16 2019',
             'vs2022' => 'Visual Studio 17 2022');

  die "ERROR: Unsupported Visual Studio version '$ver' supplied.\nStopped"
    if ! exists $map{$ver};

  return $map{$ver};
}

if ($build_gtest) {
  print("Building Google Test...\n");

  my $cwd = Cwd::getcwd();
  my $build_dir = $opts{'gtest'} . $slash . 'build';
  my $install_dir = $build_dir . $slash . 'install';
  my $cmake = $opts{'cmake'};
  my @cmake_platform_args = $is_windows ?
    ('-Dgtest_force_shared_crt=ON',
     '-DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1',
     '-G', '"' . win32_cmake_generator($opts{'compiler_version'}) . '"',
     '-A', $opts{'compiler_target_architecture'}) : ();
  my @cmake_cmds = ([$cmake,
                     "-DCMAKE_INSTALL_PREFIX=$install_dir",
                     '-DCMAKE_INSTALL_LIBDIR=lib',
                     '-DCMAKE_POSITION_INDEPENDENT_CODE=TRUE',
                     @cmake_platform_args,
                     '..'],
                    ["$cmake", "--build", ".", "--target", "install"]);

  mkdir($build_dir)
    or die "ERROR '$!': failed to make directory $build_dir from $cwd.\nStopped"
      if ! -d $build_dir;

  mkdir($install_dir)
    or die "ERROR '$!': failed to make directory $install_dir from $cwd.\nStopped"
      if ! -d $install_dir;

  my $cd_build_dir = ChangeDir->new($build_dir);
  for my $cmd (@cmake_cmds) {
    run_command($cmd, capture => [undef, dump_on_failure => 1]) == 0
      or die "ERROR: Invoking @{$cmd} failed.\nStopped";
  }

  print("Done Building Google Test\n");
}

if (exists $opts{'jboss'} && !exists $opts{'java'}) {
  die "ERROR: --java is required for --jboss (OpenDDS JMS Provider).\nStopped";
}

if (exists $opts{'jboss'} && !exists $opts{'ant'}) {
  die "ERROR: --ant is required for --jboss (OpenDDS JMS Provider).\nStopped";
}

if (exists $opts{'wireshark-cmake'}) {
  if (exists $opts{'wireshark'}) {
    die "ERROR: --wireshark and --wireshark-cmake can not be used at the same time.\nStopped";
  }

  if (! -d ($opts{'wireshark-build'} . '/' . $opts{'wireshark-lib'})) {
    if ($wireshark_lib_defaulted) {
      die "ERROR: The default value for wireshark-lib: " . $opts{'wireshark-lib'} .
          " does not exist, please supply wireshark-lib with the correct value";
    }
    else {
      die "ERROR: The supplied wireshark-lib: " . $opts{'wireshark-lib'} . " does not exist.";
    }
  }
}

if (exists $opts{'wireshark-cmake'} && !exists $opts{'wireshark-build'}) {
  die "ERROR: --wireshark-build are required with --wireshark-cmake.\nStopped";
}

if (exists $opts{'wireshark-lib'} && !exists $opts{'wireshark-cmake'}) {
  die "ERROR: --wireshark-cmake and --wireshark-build is required for --wireshark-lib.\nStopped";
}

if (exists $opts{'wireshark-build'} && !exists $opts{'wireshark-cmake'}) {
  die "ERROR: --wireshark-cmake is required for --wireshark-build.\nStopped";
}

if ($opts{'glib'}) {
  if (!-r "$opts{'glib'}/lib/glib-2.0/include/glibconfig.h") {
    my $pc = `pkg-config --cflags-only-I glib-2.0`;
    chomp $pc;
    if ($pc =~ m!-I$opts{'glib'}/([-\w/]+)/glib-2.0/include!) {
      print "pkg-config found GLIB_LIB_DIR=$1\n" if $opts{'verbose'};
      setEnv('GLIB_LIB_DIR', $1);
    }
    else {
      print "Failed to locate glibconfig.h using pkg-config ($pc).  Set the " .
        "environment variable GLIB_LIB_DIR and re-run MPC.\n";
    }
  }
}

sub push_env_dir {
  my ($buildEnvRef, $var, $path) = @_;
  if (exists $buildEnvRef->{$var}) {
    $buildEnvRef->{$var} .= $specific{'pathsep'} . $path;
  }
  else {
    $buildEnvRef->{$var} = $path;
  }
}

sub push_path {
  my ($buildEnvRef, $path) = @_;
  push_env_dir($buildEnvRef, 'PATH', $path);
}

sub push_libpath {
  my ($buildEnvRef, $path) = @_;
  my $build = $buildEnvRef->{'build'};
  my $platform = $opts{$build};
  my $libpathname = $platforminfo{$platform}->{'libpath'};
  push_env_dir($buildEnvRef, $libpathname, $path);
}

# Qt5
if (exists $opts{'qt'}) {
  my $qglobal = "QtCore" . $slash . "qglobal.h";
  my $qt_include;
  my $qt_bin; # location of Qt's code generators (like moc)
  my $qt_lib; # where linker should look for input (location of .lib or .so)
  my $qt_path; # add to PATH or LD_LIBRARY_PATH (location of .dll or .so.*)
  my $qt_bin_suffix = exists $ENV{'QT5_SUFFIX'} ? $ENV{'QT5_SUFFIX'} : '';
  my $qt_moc = "moc" . $qt_bin_suffix . $exeext;
  my $qt_default_suffix = "-qt5";
  my $qt_moc_default_suffix = "moc" . $qt_default_suffix . $exeext;
  my $qt_help_mesg = ", stopped\nPlease either install Qt or provide the correct " .
    "Qt5 locations (see docs/qt.md for details).\n";

  if ($try_to_use_qt_system_pkg) {
    # Try to use pkg-config to get Qt locations
    `pkg-config --print-variables Qt5Core`;
    if ($?) {
      die "ERROR: Trying to use system Qt package but could not confirm " .
        "Qt and/or pkg-config exists on the system$qt_help_mesg";
    }
    $qt_include = `pkg-config --variable=includedir Qt5Core`;
    die "ERROR: could not resolve system Qt include location$qt_help_mesg" if ($?);
    chomp $qt_include;
    $qt_bin = `pkg-config --variable=host_bins Qt5Core`;
    die "ERROR: could not resolve system Qt build tools location$qt_help_mesg" if ($?);
    chomp $qt_bin;
    $qt_lib = `pkg-config --variable=libdir Qt5Core`;
    die "ERROR: could not resolve system Qt lib location$qt_help_mesg" if ($?);
    chomp $qt_lib;
  }
  else {
    if (exists $opts{'qt-include'}) {
      if (!length($opts{'qt-include'})) {
        die "ERROR: --qt-include requires an argument$qt_help_mesg";
      }
      $qt_include = $opts{'qt-include'};
    }
    else {
      $qt_include = $targetEnv{'QTDIR'} . $slash . 'include';
    }

    if (exists $ENV{'QT5_BINDIR'}) {
      $qt_bin = $ENV{'QT5_BINDIR'};
    }
    else {
      $qt_bin = $targetEnv{'QTDIR'} . $slash . 'bin';
    }

    if (exists $ENV{'QT5_LIBDIR'}) {
      $qt_lib = $ENV{'QT5_LIBDIR'};
    }
    else {
      $qt_lib = $targetEnv{'QTDIR'} . $slash . ($is_windows ? 'bin' : 'lib');
    }
  }
  $qt_path = $qt_lib;

  # Try to guess if Qt vcpkg is being used
  my $vcpkg_tools = join($slash, $targetEnv{'QTDIR'}, "tools", "qt5");
  my $assume_vcpkg =
    (-r $vcpkg_tools . $slash . 'bin' . $slash . $qt_moc) &&
    !exists $ENV{'QT5_LIBDIR'} &&
    !exists $ENV{'QT5_BINDIR'};
  if ($assume_vcpkg) {
    $qt_bin = $vcpkg_tools . $slash . 'bin';
    if ($debug) {
      $qt_lib = join($slash, $targetEnv{'QTDIR'}, "debug", "lib");
      $qt_path = join($slash, $targetEnv{'QTDIR'}, "debug",
        ($is_windows ? 'bin' : 'lib'));
    }
    else {
      $qt_lib = join($slash, $targetEnv{'QTDIR'}, 'lib');
    }
  }

  # Check for Qt Headers using qglobal.h
  # Also try to detect if Qt Headers were just put in the include directory or
  # put in qt5 directory inside the include directory. For example, the former
  # might happen on Windows with prebuilt Qt while the latter might be the
  # case with Linux with Qt installed from package manager.
  if (! -r  $qt_include . $slash . $qglobal) {
    my $qt_subdir = $qt_include . $slash . 'qt5';
    if (-r $qt_subdir . $slash . $qglobal) {
      $qt_include = $qt_subdir;
    }
    else {
      die "ERROR: Could not find Qt headers at $qt_include" .
        " ($qglobal could not be found)$qt_help_mesg";
    }
  }

  # Check for Qt Tools using moc
  if (! -r $qt_bin . $slash . $qt_moc) {
    # If QT5_SUFFIX isn't defined, maybe it needs a suffix
    if ((!exists $ENV{'QT5_SUFFIX'}) &&
      (-r $qt_bin . $slash . $qt_moc_default_suffix)) {
      $qt_bin_suffix = $qt_default_suffix;
    }
    else {
      die "ERROR: Could not find Qt build tools at $qt_bin" .
        " ($qt_moc could not be found)$qt_help_mesg";
    }
  }

  setEnv('QT5_INCDIR', $qt_include);
  setEnv('QT5_BINDIR', $qt_bin);
  setEnv('QT5_SUFFIX', $qt_bin_suffix);
  setEnv('QT5_LIBDIR', $qt_lib);
  push_libpath(\%targetEnv, $qt_path);
}
elsif (exists $opts{'qt-include'}) {
  die "ERROR: --qt-include requires --qt, stopped\n";
}

sub write_host_workspace {
  my %buildEnv = %{shift()};
  my $MWC = backup_and_open($buildEnv{'DDS_ROOT'} . '/host_tools.mwc');
  print $MWC <<'EOT';
workspace {
  $(ACE_ROOT)/ace/ace.mpc
  $(ACE_ROOT)/apps/gperf/src
  $(TAO_ROOT)/TAO_IDL
  dds/DCPS/OpenDDS_Util.mpc
  dds/idl
  java/idl2jni/codegen
EOT
  print $MWC "  \$(TAO_ROOT)/tao/tao.mpc\n" if $opts{'safety-profile'};
  print $MWC "}\n";
  $MWC->close;
  print "Wrote host_tools.mwc in $buildEnv{'DDS_ROOT'}\n" if $opts{'verbose'};
  dump_and_unlink($MWC) if $opts{'dry-run'};
}

sub mergeToEnv {
  my $buildEnv = shift;
  for my $k (keys %{$buildEnv}) {
    next if $k eq 'build';
    if ($buildEnv->{$k} =~
        /^\Q$specific{'refpre'}\E$k\Q$specific{'refpost'}\E(.*)/) {
      if ($1 ne '') {
        $ENV{$k} .= $1;
        print "ENV: Appending $1 to $k\n" if $opts{'verbose'};
      }
    }
    else {
      $ENV{$k} = $buildEnv->{$k};
      print "ENV: Setting $k to $buildEnv->{$k}\n" if $opts{'verbose'};
    }
  }
}

sub disable_feature {
  my($featureArray, $feature) = @_;
  $feature =~ s/-/_/g;
  push(@{$featureArray}, "$feature=0");
}

sub get_default_features {
  my $buildEnv = shift;

  my %default_features;
  my $file = "$buildEnv->{ACE_ROOT}/bin/MakeProjectCreator/config/default.features";
  if (-e $file) {
    open(my $fh, '<', $file) or die "ERROR: Could not open $file: $!";
    while (my $row = <$fh>) {
      chomp $row;
      $row =~ s/\/\/.*//;
      $row =~ s/^\s+//;
      $row =~ s/\s+$//;
      next if $row eq '';
      my ($key, $value) = split(/\s*=\s*/, $row);
      $default_features{$key} = $value;
    }
  }
  return %default_features;
}

sub get_requested_features {
  my %requested_features;
  for my $f (@features) {
    my ($key, $value) = split(/=/, $f);
    $value = "1" unless defined $value;
    $requested_features{$key} = $value;
  }
  return %requested_features;
}

sub get_features {
  my $buildEnv = shift;

  my %features = get_default_features($buildEnv);
  my %requested_features = get_requested_features();
  for my $key (keys(%requested_features)) {
    $features{$key} = $requested_features{$key};
  }
  return %features;
}

my $buildtao;
sub generate_workspace {
  my $buildEnv = shift;
  $buildtao = $force_ace_tao;
  my $is_target = $buildEnv->{'build'} eq 'target';

  for my $feat (qw/built-in-topics ownership-profile/) {
    if (exists $opts{$feat} && !$opts{$feat}) {
      disable_feature(\@features, $feat);
    }
  }

  if ($opts{'security'}) {
    disable_feature(\@features, 'no-opendds-security');
  }

  # default of these depends on whether we are doing a safety-profile build
  for my $feat (qw/content-subscription content-filtered-topic
                   multi-topic query-condition ownership-kind-exclusive
                   object-model-profile persistence-profile
                   no-opendds-safety-profile/) {
    if (defined $opts{'no-opendds-safety-profile'}) {
      if (!(exists $opts{$feat} && $opts{$feat})) {
        disable_feature(\@features, $feat);
      }
    }
    elsif (exists $opts{$feat} && !$opts{$feat}) {
      disable_feature(\@features, $feat);
    }
  }

  if ($is_target && $opts{'target'} eq 'android') {
    # We can't detect C++11 compatibility in cross compilers at the moment,
    # but all the NDKs we officially support are C++11+ by default, so enable
    # C++11 features unless an explicit 'no_cxx11' feature already is set.
    my $set = 1;
    for my $feature (@{$opts{'features'}}) {
      if ($feature =~ /no_cxx11/) {
        $set = 0;
      }
    }
    disable_feature(\@features, 'no_cxx11') if ($set);
    if ($opts{'verbose'}) {
      print "Setting no_cxx11=0 for Android: $set\n";
    }

    # Disable including $JAVA_HOME/include on Android. Android NDK includes a
    # jni.h
    disable_feature(\@features, 'jni_include')
  }

  my $mpctype = (!$is_windows || ($cross_compile && $is_target))
                ? 'gnuace' : $opts{'compiler_version'};

  if ($mpctype eq 'gnuace') {
    my $UM;
    if (@features || $cross_compile) {
      print "Writing $buildEnv->{'DDS_ROOT'}/user_macros.GNU\n"
        if $opts{'verbose'};
      $UM = backup_and_open($buildEnv->{'DDS_ROOT'} . '/user_macros.GNU');
    }
    if ($is_target) {
      for my $feat (@features) {
        my $key = $feat;
        $key =~ s/=.*//;
        print $UM ($feat =~ /=/ ? $feat : "$feat=1"), "\n"
          unless $need_platform_macros{$key};
      }
      if ($cross_compile) {
        print $UM <<'EOT';
OPENDDS_IDL = $(HOST_DDS)/bin/opendds_idl
OPENDDS_IDL_DEP = $(OPENDDS_IDL)$(HOST_EXE_EXT)
IDL2JNI = $(HOST_DDS)/bin/idl2jni
IDL2JNI_DEP = $(IDL2JNI)$(HOST_EXE_EXT)
CROSS-COMPILE = 1
EOT
      }
    }
    $UM->close if $UM;
    dump_and_unlink($UM) if $UM && $opts{'dry-run'};
  }

  if ($opts{'safety-profile'} && $is_target) {
    $buildtao = 0; # TAO will be built separately
  }
  else {
    # build tao if tao_idl does not exist
    $buildtao = $buildtao || !-x "$buildEnv->{'ACE_ROOT'}/bin/tao_idl$exeext";
  }

  locate_mpc($ace_src);

  # Append to default.features
  if ($is_target) {
    push(@features, 'cross_compile') if $cross_compile;

    if ($platforminfo{$opts{'target'}}->{'needs_i2jrt_corba'} && $opts{'java'}) {
      if ($opts{'verbose'}) {
        print "Target platform needs i2jrt_corba.jar, forcing java_pre_jpms=0 to get it\n";
      }
      my $found = 0;
      for my $i (0 .. scalar $#features) {
        if ($features[$i] =~ /^java_pre_jpms/) {
          $features[$i] = 'java_pre_jpms=0';
          my $found = 1;
        }
      }
      if (!$found) {
        push(@features, "java_pre_jpms=0");
      }
    }

    if ($opts{'java'} && $cross_compile) {
      push(@features, "jni_check=0");
    }

    if ($buildtao && @features) {
      my $df_file = $buildEnv->{'ACE_ROOT'} .
        '/bin/MakeProjectCreator/config/default.features';
      my $DF = $opts{'dry-run'} ? File::Temp->new()
        : new FileHandle(">>$df_file");
      for my $f (@features) {
        print $DF ($f =~ /=/ ? $f : "$f=1"), "\n";
      }
      $DF->close;
      if ($opts{'verbose'}) {
        print '' . ($wrote_df ? 'Appended to' : 'Wrote') .
          " $buildEnv->{'ACE_ROOT'}/.../default.features\n";
      }
      dump_and_unlink($DF) if $opts{'dry-run'};
    }

    if (!$buildtao && !defined $opts{'safety-profile'}) {
      # Check for compatibility between the requested features/macros
      # and the pre-built ACE/TAO.
      my %existing_features = get_default_features($buildEnv);
      my %requested_features = get_requested_features();
      for my $f (@ace_features) {
        if (defined $requested_features{$f}) {
          if (!defined $existing_features{$f}) {
            die "ERROR: Requested feature $f not defined for ACE/TAO";
          }
          elsif ($requested_features{$f} != $existing_features{$f}) {
            die "ERROR: Feature $f set to $requested_features{$f} but has " .
              "value $existing_features{$f} in ACE/TAO";
          }
        }
      }

      my $file = "$buildEnv->{'ACE_ROOT'}/include/makeinclude/platform_macros.GNU";
      my %existing_macros;
      if (-e $file) {
        open(my $fh, '<', $file) or die "ERROR: Could not open $file: $!";
        while (my $row = <$fh>) {
          chomp $row;
          $row =~ s/\s*#.*$//;
          if ($row =~ /^\s*(\w+)\s*[:?+]?=\s*(.*)$/) {
            my ($key, $value) = ($1, $2);
            $existing_macros{$key} = $value;
          }
        }
        my %requested_macros;
        for my $f (@platformmacros, @{$opts{'macros'}}) {
          my ($key, $value) = split(/=/, $f);
          $value = "1" unless defined $value;
          $requested_macros{$key} = $value;
        }
        for my $key (@ace_macros) {
          if (exists $opts{$key}) {
            my $macro = ($key eq 'static') ? 'static_libs_only' : $key;
            $requested_macros{$macro} = $opts{$key};
          }
        }
        for my $f (@ace_macros) {
          if (defined $requested_macros{$f}) {
            if (!defined $existing_macros{$f}) {
              die "ERROR: Requested macro $f not defined for ACE/TAO";
            }
            elsif ($requested_macros{$f} != $existing_macros{$f}) {
              die "ERROR: Macro $f set to $requested_macros{$f} but has " .
                "value $existing_macros{$f} in ACE/TAO";
            }
          }
        }
      }
    }
  }

  my $custom_ws = "OpenDDS_custom.mwc";
  if (exists($opts{'workspace'})) {
    backup_and_copy($opts{'workspace'}, $custom_ws);
  }

  my $ws;
  if (-r "$buildEnv->{'DDS_ROOT'}/host_tools.mwc") {
    $ws = 'host_tools.mwc';
  }
  elsif (-r $custom_ws) {
    $ws = $custom_ws;
  }
  elsif (!$tests && $buildtao) {
    $ws = 'DDS_TAOv2.mwc';
  }
  elsif (!$tests && !$buildtao) {
    $ws = 'DDS_no_tests.mwc';
  }
  elsif ($buildtao) {
    $ws = 'DDS_TAOv2_all.mwc';
  }
  else {
    $ws = 'DDS.mwc';
  }

  my $static = (($opts{'static'} && $is_windows) ||
    ($cross_compile && $buildEnv->{'build'} eq 'host' && $mpctype ne 'gnuace'));

  my @mpcopts = ();
  if (defined $opts{'mpcopts'}) {
    for my $i (@{$opts{'mpcopts'}}) {
      push(@mpcopts, split(/ /, $i));
    }
  }

  # We are not using CIAO or DAnCE, but MPC.cfg expands $CIAO_ROOT and
  # $DANCE_ROOT so leaving them empty/undefined would cause /MPC/config
  # to be on the include path for .mpb files.
  for my $var ('CIAO_ROOT', 'DANCE_ROOT') {
    $buildEnv->{$var} = 'unused' unless defined $buildEnv->{$var};
  }

  my %savedEnv = %ENV;
  print "ENV: saving current environment\n" if $opts{'verbose'};
  mergeToEnv($buildEnv);

  my @mwccmd = ('perl', "$ENV{'ACE_ROOT'}/bin/mwc.pl", '-type', "$mpctype");
  if ($static) {
    push(@mwccmd, '-static');
  }

  my @mwcargs = ("$buildEnv->{'DDS_ROOT'}$slash$ws", @mpcopts);
  if (!$buildtao && @features) {
    for my $feature (@features) {
      push(@mwcargs, '-features', ($feature =~ /=/ ? $feature : "$feature=1"));
    }
  }

  print 'Running MPC to generate ', ($mpctype eq 'gnuace' ? 'makefiles' :
                                     'project files'), ".\n";
  if (run_command([@mwccmd, @mwcargs])) {
    die "ERROR: Error from MPC, stopped";
  }
  $buildEnv->{'mpctype'} = $mpctype;

  # If this is a target safety profile build
  if (defined $opts{'no-opendds-safety-profile'}) {
    # Generate ACE workspace separately, to exclude TAO
    if (run_command([@mwccmd, "$buildEnv->{'ACE_ROOT'}/ace", @mpcopts])) {
      die "ERROR: Error from MPC, stopped";
    }
  }

  %ENV = %savedEnv;
  print "ENV: restoring previous environment\n" if $opts{'verbose'};

  $ws =~ s/\.mwc$/.sln/;
  $opts{'solution_file'} = $ws;
}

sub convert_environment {
  my $val = shift;
  $$val =~ s/\\/\//g;
  $$val =~ s/%(\w+)%/\$\{$1\}/g;
}

sub write_environment {
  my %buildEnv = %{shift()};
  my $dir = shift;
  my $MK = undef;
  if ($buildEnv{'mpctype'} eq 'gnuace') {
    if (!$opts{'dry-run'}) {
      move($dir . '/GNUmakefile', $dir . '/GNUmakefile.dist');
    }
    $MK = backup_and_open($dir . '/GNUmakefile');
  }

  my $SE = backup_and_open($dir . '/setenv.' . $specific{'ext'});

  my $args_ = join(' ', map {/ / ? ('"' . $_ . '"') : $_} @ARGS);
  print $SE "$specific{'comment'} OpenDDS configure script: $0 $args_\n";
  print $MK "$specific{'comment'} OpenDDS configure script: $0 $args_\n" if $MK;

  for my $key (sort(keys(%buildEnv))) {
    if ($key ne 'build' && $key ne 'mpctype') {
      my $value = $buildEnv{$key};
      $value =~ s/^"(.*)"$/$1/g;

      if ($is_windows) {
        print $SE "set \"$key=$value\"\n";
      }
      else {
        my $posix_value = $value;
        $posix_value =~ s/"/\\"/g;
        print $SE "export $key=\"$posix_value\"\n";
      }

      if ($MK) {
        convert_environment(\$value) if $is_windows;
        print $MK "export $key := $value\n";
      }
    }
  }

  $SE->close;
  print "Wrote $dir/setenv.$specific{'ext'}\n" if $opts{'verbose'};
  dump_and_unlink($SE) if $opts{'dry-run'};

  if ($MK) {
    print $MK "include GNUmakefile.dist\n";
    $MK->close;
    print "Wrote $dir/GNUmakefile, wrapping original GNUmakefile.dist\n"
      if $opts{'verbose'};
    dump_and_unlink($MK) if $opts{'dry-run'};
  }
}

sub check_mac_version {
  my $buildEnvRef = shift;
  if (-r "$buildEnvRef->{ACE_ROOT}/ace/config-macosx.h") {
    my $cfg = new FileHandle("$buildEnvRef->{ACE_ROOT}/ace/config-macosx.h");
    while (<$cfg>) {
      if (/__MAC_OS_X_VERSION_MAX_ALLOWED/) {
        return;
      }
    }
  }

  die "ERROR: This version of ACE doesn't contain the required configuration " .
    "files for Apple macOS.  Stopped";
}

sub configure_build {
  my $buildEnvRef = shift;
  if (-r "$buildEnvRef->{ACE_ROOT}/ace/config.h" && !$force_ace_tao) {
    print "ACE_ROOT/ace/config.h exists, skipping configuration of ACE+TAO\n";
  }
  else {
    check_mac_version($buildEnvRef) if $opts{'host'} eq 'macosx';

    write_config_h($buildEnvRef);
    write_default_features($buildEnvRef);
    write_platform_macros($buildEnvRef);
  }
  generate_workspace($buildEnvRef);
  write_environment($buildEnvRef, $buildEnvRef->{'DDS_ROOT'});

  if ($buildEnvRef->{'build'} eq 'target') {
    write_cmake_file($buildEnvRef);
  }
}

sub write_cross_compile_makefile {
  my $MF = backup_and_open('GNUmakefile');
  my $safety_profile_step = $opts{'safety-profile'} ?
    "\tcd $targetEnv{'ACE_ROOT'}/ace && " .
    '$(MAKE) $(if $(filter all,$@),ACE,$@)' . "\n" : '';
  print $MF <<"EOT";
# OpenDDS configure script: $0 @ARGS
all clean realclean depend:
\tcd build/host && \$(MAKE) \$@
$safety_profile_step\tcd build/target && \$(MAKE) \$@
.PHONY: all clean realclean depend
EOT
  $MF->close;
  print "Wrote top-level GNUmakefile for cross-compile\n" if $opts{'verbose'};
  dump_and_unlink($MF) if $opts{'dry-run'};
}

sub nested {
  my($sub, $top) = @_;
  my $subdir = $opts{'dry-run'} ? $sub : Cwd::abs_path($sub);
  my $parent = $opts{'dry-run'} ? $top : Cwd::abs_path($top);
  if ($opts{'dry-run'} && $top eq '.' && looksRelative($sub)) {
    return $slash . $sub;
  }
  if (index($subdir, $parent) == 0) {
    return substr($subdir, length($parent));
  }
  return undef;
}

sub add_dependency_paths {
  my %dirs;
  for my $key ('openssl', 'xerces3') {
    if ($opts{$key}) {
      next if $opts{$key} eq '/usr' || $opts{$key} eq 'skip_version_check';
      my $suffix = $slash . 'bin';
      if ($opts{$key . '-debugbin'}) {
        $suffix = $slash . 'debug' . $suffix;
      }
      $dirs{$targetEnv{$optdep{$key}->{env}} . $suffix} = 1;
    }
  }
  for my $d (keys %dirs) {
    push_libpath(\%targetEnv, $d);
  }

  if ($opts{'openssl'} && $ENV{'SSL_LIBDIR'}) {
    push_libpath(\%targetEnv, $ENV{'SSL_LIBDIR'});
  }
}

my $cloned_build = $cross_compile && $build_host_tools;
if ($cloned_build) {
  clone_host_and_target('.');
  setHostEnv('DDS_ROOT', 'build/host');
  setEnv('HOST_DDS', 'build/host');
  setEnv('DDS_ROOT', 'build/target');

  my $tao_clone = $opts{'safety-profile'} ? 'host' : 'target';
  my $ace_sub_dds = nested($ace_src, '.');
  my $tao_sub_dds = nested($tao_src, '.');
  my $tao_sub_ace = nested($tao_src, $ace_src);

  if ($ace_sub_dds) {
    setHostEnv('ACE_ROOT', 'build/host' . $ace_sub_dds);
    setEnv('HOST_ACE', 'build/host' . $ace_sub_dds);
    setEnv('ACE_ROOT', 'build/target' . $ace_sub_dds);
  }
  else {
    clone_host_and_target($ace_src);
    setHostEnv('ACE_ROOT', $ace_src . '/build/host');
    setEnv('HOST_ACE', $ace_src . '/build/host');
    setEnv('ACE_ROOT', $ace_src . '/build/target');
    if ($tao_sub_ace) {
      setHostEnv('TAO_ROOT', $ace_src . '/build/host' . $tao_sub_ace);
      setEnv('TAO_ROOT', $ace_src . '/build/' . $tao_clone . $tao_sub_ace);
    }
  }

  if ($tao_sub_dds) {
    setHostEnv('TAO_ROOT', 'build/host' . $tao_sub_dds);
    setEnv('TAO_ROOT', 'build/' . $tao_clone . $tao_sub_dds);
  }
  elsif (!$tao_sub_ace) {
    if ($opts{'safety-profile'}) {
      setHostEnv('TAO_ROOT', $tao_src);
      setEnv('TAO_ROOT', $tao_src);
    }
    else {
      clone_host_and_target($tao_src);
      setHostEnv('TAO_ROOT', $tao_src . '/build/host');
      setEnv('TAO_ROOT', $tao_src . '/build/target');
    }
  }

  push_path(\%targetEnv, $hostEnv{'ACE_ROOT'} . $slash . 'bin');
  push_path(\%targetEnv, $hostEnv{'DDS_ROOT'} . $slash . 'bin');

  print "Cross-compile configuring host\n";

  write_host_workspace(\%hostEnv); # host_tools.mwc
  configure_build(\%hostEnv);

  print "Cross-compile configuring target\n";

  if ($opts{'safety-profile'}) {
    $opts{'no-opendds-safety-profile'} = 0; # only set for target
  }

  unless ($opts{'static'}) {
    push_libpath(\%targetEnv, $targetEnv{'ACE_ROOT'} . $slash . 'lib');
    push_libpath(\%targetEnv, $targetEnv{'DDS_ROOT'} . $slash . 'lib');

    add_dependency_paths();
  }

  configure_build(\%targetEnv);

  write_environment(\%targetEnv, $targetEnv{'ACE_ROOT'} . '/ace')
    if $opts{'safety-profile'};

  write_cross_compile_makefile() if !$is_windows;
}
else { # does not require cloned builds
  setEnv('ACE_ROOT', $ace_src);
  setEnv('TAO_ROOT', $tao_src);
  setEnv('DDS_ROOT', '.');

  if (!$build_host_tools && $opts{'host-tools'}) {
    setEnv('HOST_DDS', $opts{'host-tools'});
  }

  push_path(\%targetEnv, $targetEnv{'ACE_ROOT'} . $slash . 'bin');
  push_path(\%targetEnv, $targetEnv{'DDS_ROOT'} . $slash . 'bin');

  unless ($opts{'static'}) {
    push_libpath(\%targetEnv, $targetEnv{'ACE_ROOT'} . $slash . 'lib');
    push_libpath(\%targetEnv, $targetEnv{'DDS_ROOT'} . $slash . 'lib');

    add_dependency_paths();
  }

  if ($host_tools_only) {
    write_host_workspace(\%targetEnv);
  }

  configure_build(\%targetEnv);
}

print "Completed configuring OpenDDS, next ";
if (!$is_windows || ($cross_compile && !$cloned_build)) {
  print "run '", (($^O eq 'solaris' || $^O eq 'freebsd') ? 'g' : ''), "make'";
}
elsif ($cloned_build) {
  print "compile build\\host\\host_tools.sln\n",
    "using the environment in build\\host\\setenv.cmd.\n",
    "Then build OpenDDS for $opts{'target'} by running make in ",
    "build\\target.\n";
  exit 0;
}
else {
  print "build '", $opts{'solution_file'}, "' using the '",
    ($opts{'optimize'} ? 'Release' : 'Debug'), "' configuration";
}

print " to compile ",
  ($cloned_build ? "\nthe host and target builds of " : ''),
  "OpenDDS", (($buildtao || $cloned_build) ? ' and ACE+TAO' : '') ,".\n",
  "You can use the generated setenv.$specific{'ext'} script ",
  ($cloned_build ?
   "in the\nbuild${slash}host and build${slash}target dirs " : ''),
  "to set environment\nvariables for future shell sessions.\n";
if ($is_windows && !$cross_compile) {
  print "Start Visual Studio from this command prompt so that it inherits ",
    "the correct\nenvironment variables. Try running \"devenv $opts{'solution_file'}\".\n";
}

sub write_cmake_file {
  my $buildEnv = shift();

  my $module_dir = "$buildEnv->{'DDS_ROOT'}/cmake";
  mkpath($module_dir) unless -d $module_dir;
  my $fh = backup_and_open($module_dir . '/config.cmake');

  sub whack_to_slash {
    my $s = shift;
    $s =~ s|\\|/|g;
    return $s;
  }

  sub to_cmake_scalar {
    my $val = shift;
    return 'ON' if "$val" eq '1';
    return 'OFF' if "$val" eq '0';
    return '"' . whack_to_slash($val) . '"';
  }

  sub to_cmake_list {
    my @list = @{$_[0]};
    return '"' . join(';', map {whack_to_slash($_)} @list) . '"';
  }

  sub to_cmake_value {
    my $value = shift();

    my $ref_type = ref($value);
    if ($ref_type eq 'ARRAY') {
      return to_cmake_list($value);
    }
    elsif ($ref_type eq '') {
      return to_cmake_scalar($value);
    }
    else {
      die "ERROR: to_cmake_value invalid value type, stopped";
    }
  }

  sub print_cmake_config {
    my $fh = shift();
    my $name = shift();
    my $value = shift();

    $name = uc("OPENDDS_$name");
    $name =~ s/-/_/g;

    print $fh "set($name ", to_cmake_value($value), ")\n";
  }

  sub generate_config_cmake {
    my $fh = shift();
    my $buildEnv = shift();

    print_cmake_config($fh, 'MPC', Cwd::abs_path($buildEnv->{'MPC_ROOT'}));
    print_cmake_config($fh, 'ACE', Cwd::abs_path($buildEnv->{'ACE_ROOT'}));
    print_cmake_config($fh, 'TAO', Cwd::abs_path($buildEnv->{'TAO_ROOT'}));

    print $fh "\n# Based on MPC features\n";
    my @configs = (
      {
        name => 'CXX11',
        feature => 'no_cxx11',
        inverted => 1,
      },
      {
        name => 'WCHAR',
        feature => 'uses_wchar',
      },
      {
        name => 'IPV6',
        feature => 'ipv6',
      },
      {
        name => 'SAFETY_PROFILE',
        feature => 'no_opendds_safety_profile',
        inverted => 1,
      },
      {
        name => 'VERSIONED_NAMESPACE',
        feature => 'versioned_namespace',
      },
      {
        name => 'SUPPRESS_ANYS',
        feature => 'dds_suppress_anys',
        default => 1,
      },
      {
        name => 'COVERAGE',
        feature => 'dds_non_coverage',
        inverted => 1,
      },
    );
    my %features = get_features($buildEnv);
    for my $config (@configs) {
      my $inverted = $config->{inverted} // 0;
      my $default = $config->{default} // 0;
      my $enabled;
      if (exists($features{$config->{feature}})) {
        $enabled = $features{$config->{feature}} eq "1" ? !$inverted : $inverted;
      }
      else {
        $enabled = $default;
      }
      print_cmake_config($fh, $config->{name}, $enabled);
    }

    # TODO(iguessthislldo): Move to a smarter system that can use existing
    # ACE/TAO and that works with cross compiling.
    print $fh "\n# Based on configure script options\n";
    my %opts_to_use = (
      'built-in-topics' => 1,
      'compiler' => '',
      'content-filtered-topic' => 1,
      'content-subscription' => 1,
      'debug' => 1,
      'features' => '',
      'gtest' => '',
      'inline' => 1,
      'java' => '',
      'mpcopts' => '',
      'multi-topic' => 1,
      'object-model-profile' => 1,
      'openssl' => '',
      'ownership-kind-exclusive' => 1,
      'ownership-profile' => 1,
      'persistence-profile' => 1,
      'qt' => '',
      'query-condition' => 1,
      'rapidjson' => '',
      'security' => 0,
      'static' => 0,
      'std' => '',
      'xerces3' => '',
    );
    for my $opt (sort(keys(%opts_to_use))) {
      print_cmake_config($fh, $opt, exists($opts{$opt}) ? $opts{$opt} : $opts_to_use{$opt});
    }

    print $fh "\n# Sanitizers\n";
    for my $name (sort(keys(%all_sanitizers))) {
      print_cmake_config($fh, $name, exists($enabled_sanitizers{$name}) ? 1 : 0);
    }
    if (scalar(@fsanitize)) {
      print_cmake_config($fh, 'SANITIZER_COMPILER_ARGS', \@sanitizer_compiler_args);
      print_cmake_config($fh, 'SANITIZER_LINKER_ARGS', \@sanitizer_linker_args);
    }
  };

  print $fh <<"EOF";
# Contains configuration variables for the FindOpenDDS cmake module based on
# detected defaults and/or user-supplied switches. Feel free to edit them as
# necessary, but keep in mind that they will be moved to config.cmake.bak.*
# the next time the \$DDS_ROOT/configure script is executed.
#
# THIS FILE WAS AUTO-GENERATED BY: $0 @ARGS

EOF
  generate_config_cmake($fh, $buildEnv);
  dump_and_unlink($fh) if $opts{'dry-run'};
}
