#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
#
# gen_win_dependencies.py
#
#   base class for generating windows projects, containing the
#   dependency locator code shared between the test runner and
#   the make file generators
#

import os
import sys
import fnmatch
import re
import subprocess
import string
from collections import namedtuple

if sys.version_info[0] >= 3:
  # Python >=3.0
  from io import StringIO
else:
  # Python <3.0
  try:
    from cStringIO import StringIO
  except ImportError:
    from StringIO import StringIO

import gen_base
import ezt

UserMacro = namedtuple('UserMacro', ['name', 'value'])

class SVNCommonLibrary:

  def __init__(self, name, include_dirs, lib_dir, lib_name, version=None,
               debug_lib_dir=None, debug_lib_name=None, dll_dir=None,
               dll_name=None, debug_dll_dir=None, debug_dll_name=None,
               defines=[], forced_includes=[], extra_bin=[], internal=False):
    self.name = name
    if include_dirs:
      self.include_dirs = include_dirs if isinstance(include_dirs, list) \
                                       else [include_dirs]
    else:
      self.include_dirs = []
    self.defines = defines if not defines or isinstance(defines, list) else [defines]
    self.lib_dir = lib_dir
    self.lib_name = lib_name
    self.version = version
    self.dll_dir = dll_dir
    self.dll_name = dll_name

    self.forced_includes = forced_includes if not forced_includes \
                                           or isinstance(forced_includes, list) \
                                           else [forced_includes]

    if debug_lib_dir:
      self.debug_lib_dir = debug_lib_dir
    else:
      self.debug_lib_dir = lib_dir

    if debug_lib_name:
      self.debug_lib_name = debug_lib_name
    else:
      self.debug_lib_name = lib_name

    if debug_dll_dir:
      self.debug_dll_dir = debug_dll_dir
    else:
      self.debug_dll_dir = dll_dir

    if debug_dll_name:
      self.debug_dll_name = debug_dll_name
    else:
      self.debug_dll_name = dll_name

    self.extra_bin = extra_bin
    self.internal = internal

class GenDependenciesBase(gen_base.GeneratorBase):
  """This intermediate base class exists to be instantiated by win-tests.py,
  in order to obtain information from build.conf and library paths without
  actually doing any generation."""
  _extension_map = {
    ('exe', 'target'): '.exe',
    ('exe', 'object'): '.obj',
    ('lib', 'target'): '.dll',
    ('lib', 'object'): '.obj',
    ('pyd', 'target'): '.pyd',
    ('pyd', 'object'): '.obj',
    ('so', 'target'): '.so',
    ('so', 'object'): '.obj',
    }

  _libraries = {}     # Dict of SVNCommonLibrary instances of found libraries

  _optional_libraries = [  # List of optional libraries to suppress warnings
        'db',
        'intl',
        'serf',
        'sasl',
        'swig',
        'perl',
        'python',
        'py3c',
        'ruby',
        'java_sdk',
        'openssl',
        'apr_memcache',

        # So optional, we don't even have any code to detect them on Windows
        'magic',
        'macos-plist',
        'macos-keychain',
  ]

  # When build.conf contains a 'when = SOMETHING' where SOMETHING is not in
  # this list, then the project is not generated on Windows.
  _windows_when = [
     'INSTALL_APACHE_MODS',
     # not 'SVN_USE_GMOCK',
  ]

  def parse_options(self, options):
    self.apr_path = 'apr'
    self.apr_util_path = 'apr-util'
    self.apr_iconv_path = 'apr-iconv'
    self.serf_path = None
    self.bdb_path = None
    self.httpd_path = None
    self.libintl_path = None
    self.zlib_path = 'zlib'
    self.openssl_path = None
    self.jdk_path = None
    self.junit_path = None
    self.swig_path = None
    self.py3c_path = None
    self.vs_version = '2002'
    self.sln_version = '7.00'
    self.vcproj_version = '7.00'
    self.vcproj_extension = '.vcproj'
    self.sqlite_path = 'sqlite-amalgamation'
    self.skip_sections = { 'mod_dav_svn': None,
                           'mod_authz_svn': None,
                           'mod_dontdothat' : None,
                           'libsvn_auth_kwallet': None,
                           'libsvn_auth_gnome_keyring': None }

    # Instrumentation options
    self.disable_shared = None
    self.static_apr = None
    self.static_openssl = None
    self.instrument_apr_pools = None
    self.instrument_purify_quantify = None
    self.sasl_path = None
    self.cpp_defines = []
    self.user_macros = []

    # NLS options
    self.enable_nls = None

    for opt, val in options:
      if opt == '--with-berkeley-db':
        self.bdb_path = val
      elif opt == '--with-apr':
        self.apr_path = val
      elif opt == '--with-apr-util':
        self.apr_util_path = val
      elif opt == '--with-apr-iconv':
        self.apr_iconv_path = val
      elif opt == '--with-serf':
        self.serf_path = val
      elif opt == '--with-httpd':
        self.httpd_path = val
        del self.skip_sections['mod_dav_svn']
        del self.skip_sections['mod_authz_svn']
        del self.skip_sections['mod_dontdothat']
      elif opt == '--with-libintl':
        self.libintl_path = val
        self.enable_nls = 1
      elif opt == '--with-jdk':
        self.jdk_path = val
      elif opt == '--with-junit':
        self.junit_path = val
      elif opt == '--with-zlib':
        self.zlib_path = val
      elif opt == '--with-swig':
        self.swig_path = val
      elif opt == '--with-py3c':
        self.py3c_path = val
      elif opt == '--with-sqlite':
        self.sqlite_path = val
      elif opt == '--with-sasl':
        self.sasl_path = val
      elif opt == '--with-openssl':
        self.openssl_path = val
      elif opt == '--enable-purify':
        self.instrument_purify_quantify = 1
        self.instrument_apr_pools = 1
      elif opt == '--enable-quantify':
        self.instrument_purify_quantify = 1
      elif opt == '--enable-pool-debug':
        self.instrument_apr_pools = 1
      elif opt == '--enable-nls':
        self.enable_nls = 1
      elif opt == '--disable-shared':
        self.disable_shared = 1
      elif opt == '--with-static-apr':
        self.static_apr = 1
      elif opt == '--with-static-openssl':
        self.static_openssl = 1
      elif opt == '-D':
        self.cpp_defines.append(val)
      elif opt == '--vsnet-version':
        if val == '2002' or re.match(r'^7(\.\d+)?$', val):
          self.vs_version = '2002'
          self.sln_version = '7.00'
          self.vcproj_version = '7.00'
          self.vcproj_extension = '.vcproj'
        elif val == '2003' or re.match(r'^8(\.\d+)?$', val):
          self.vs_version = '2003'
          self.sln_version = '8.00'
          self.vcproj_version = '7.10'
          self.vcproj_extension = '.vcproj'
        elif val == '2005' or re.match(r'^9(\.\d+)?$', val):
          self.vs_version = '2005'
          self.sln_version = '9.00'
          self.vcproj_version = '8.00'
          self.vcproj_extension = '.vcproj'
        elif val == '2008' or re.match(r'^10(\.\d+)?$', val):
          self.vs_version = '2008'
          self.sln_version = '10.00'
          self.vcproj_version = '9.00'
          self.vcproj_extension = '.vcproj'
        elif val == '2010':
          self.vs_version = '2010'
          self.sln_version = '11.00'
          self.vcproj_version = '10.0'
          self.vcproj_extension = '.vcxproj'
        elif val == '2012' or val == '11':
          self.vs_version = '2012'
          self.sln_version = '12.00'
          self.vcproj_version = '11.0'
          self.vcproj_extension = '.vcxproj'
        elif val == '2013' or val == '12':
          self.vs_version = '2013'
          self.sln_version = '12.00'
          self.vcproj_version = '12.0'
          self.vcproj_extension = '.vcxproj'
        elif val == '2015' or val == '14':
          self.vs_version = '2015'
          self.sln_version = '12.00'
          self.vcproj_version = '14.0'
          self.vcproj_extension = '.vcxproj'
        elif val == '2017' or val == '15':
          self.vs_version = '2017'
          self.sln_version = '12.00'
          self.vcproj_version = '14.1'
          self.vcproj_extension = '.vcxproj'
        elif val == '2019' or val == '16':
          self.vs_version = '2019'
          self.sln_version = '12.00'
          self.vcproj_version = '14.2'
          self.vcproj_extension = '.vcxproj'
        elif re.match(r'^20\d+$', val):
          print('WARNING: Unknown VS.NET version "%s",'
                ' assuming VS2012. Your VS can probably upgrade')
          self.vs_version = '2012'
          self.sln_version = '12.00'
          self.vcproj_version = '11.0'
          self.vcproj_extension = '.vcxproj'
        elif re.match(r'^1\d+$', val):
          self.vs_version = val
          self.sln_version = '12.00'
          self.vcproj_version = val + '.0'
          self.vcproj_extension = '.vcxproj'
        else:
          print('WARNING: Unknown VS.NET version "%s",'
                 ' assuming "%s"\n' % (val, '7.00'))


  def __init__(self, fname, verfname, options, find_libs=True):

    # parse (and save) the options that were passed to us
    self.parse_options(options)

    # Initialize parent
    gen_base.GeneratorBase.__init__(self, fname, verfname, options)

    # These files will be excluded from the build when they're not
    # explicitly listed as project sources.
    self._excluded_from_build = frozenset(self.private_includes
                                          + self.private_built_includes)

    if find_libs:
      self.find_libraries(False)

  def find_libraries(self, show_warnings):
    "find required and optional libraries"

    # Required dependencies
    self._find_apr()
    self._find_apr_util_etc()
    self._find_zlib()
    self._find_sqlite(show_warnings)
    self._find_lz4()
    self._find_utf8proc()

    # Optional dependencies
    self._find_httpd(show_warnings)
    self._find_bdb(show_warnings)
    self._find_openssl(show_warnings)
    self._find_serf(show_warnings)
    self._find_sasl(show_warnings)
    self._find_libintl(show_warnings)

    self._find_jdk(show_warnings)

    # Swig (optional) dependencies
    if self._find_swig(show_warnings):
      self._find_perl(show_warnings)
      # py3c is required to build python bindings, show check it first
      if self._find_py3c(show_warnings):
        self._find_python(show_warnings)
      self._find_ruby(show_warnings)

  def _find_apr(self):
    "Find the APR library and version"

    minimal_apr_version = (1, 4, 0)

    if not self.apr_path:
      sys.stderr.write("ERROR: Use '--with-apr' option to configure APR " + \
                       "location.\n")
      sys.exit(1)

    inc_base = os.path.join(self.apr_path, 'include')

    if os.path.isfile(os.path.join(inc_base, 'apr-1', 'apr_version.h')):
      inc_path = os.path.join(inc_base, 'apr-1')
    elif os.path.isfile(os.path.join(inc_base, 'apr_version.h')):
      inc_path = inc_base
    else:
      sys.stderr.write("ERROR: 'apr_version' not found.\n")
      sys.stderr.write("Use '--with-apr' option to configure APR location.\n")
      sys.exit(1)

    version_file_path = os.path.join(inc_path, 'apr_version.h')
    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+APR_MAJOR_VERSION\s+(\d+)', txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+APR_MINOR_VERSION\s+(\d+)', txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+APR_PATCH_VERSION\s+(\d+)', txt, re.M)
    patch = int(vermatch.group(1))

    version = (major, minor, patch)
    self.apr_version = apr_version = '%d.%d.%d' % version

    if version < minimal_apr_version:
      sys.stderr.write("ERROR: apr %s or higher is required "
                       "(%s found)\n" % (
                          '.'.join(str(v) for v in minimal_apr_version),
                          self.apr_version))
      sys.exit(1)

    suffix = ''
    if major > 0:
        suffix = '-%d' % major

    defines = []
    if self.static_apr:
      lib_name = 'apr%s.lib' % suffix
      lib_dir = os.path.join(self.apr_path, 'LibR')
      dll_dir = None
      debug_dll_dir = None
      dll_name = None
      defines.extend(["APR_DECLARE_STATIC"])

      if not os.path.isdir(lib_dir) and \
         os.path.isfile(os.path.join(self.apr_path, 'lib', lib_name)):
        # Installed APR instead of APR-Source
        lib_dir = os.path.join(self.apr_path, 'lib')
        debug_lib_dir = None
      else:
        debug_lib_dir = os.path.join(self.apr_path, 'LibD')
    else:
      lib_name = 'libapr%s.lib' % suffix

      if os.path.isfile(os.path.join(self.apr_path, 'lib', lib_name)):
        # Installed APR instead of APR-Source
        lib_dir = os.path.join(self.apr_path, 'lib')
        debug_lib_dir = None
      else:
        lib_dir = os.path.join(self.apr_path, 'Release')
        if os.path.isfile(os.path.join(self.apr_path, 'Debug', lib_name)):
          debug_lib_dir = os.path.join(self.apr_path, 'Debug')
        else:
          debug_lib_dir = None

      dll_name = 'libapr%s.dll' % suffix
      if os.path.isfile(os.path.join(lib_dir, dll_name)):
        dll_dir = lib_dir
        debug_dll_dir = debug_lib_dir
      else:
        dll_dir = os.path.join(self.apr_path, 'bin')
        debug_dll_dir = None

    extra_bin = []

    if dll_dir:
      bin_files = os.listdir(dll_dir)
      if debug_dll_dir and os.path.isdir(debug_dll_dir):
        debug_bin_files = os.listdir(debug_dll_dir)
      else:
        debug_bin_files = bin_files

      for bin in bin_files:
        if bin in debug_bin_files:
          if re.match('^(lib)?apr[-_].*' + suffix + '(d)?.dll$', bin):
            extra_bin.append(bin)

    self._libraries['apr'] = SVNCommonLibrary('apr', inc_path, lib_dir, lib_name,
                                              apr_version,
                                              debug_lib_dir=debug_lib_dir,
                                              dll_dir=dll_dir,
                                              dll_name=dll_name,
                                              debug_dll_dir=debug_dll_dir,
                                              defines=defines,
                                              extra_bin=extra_bin)

  def _find_apr_util_etc(self):
    "Find the APR-util library and version"

    minimal_aprutil_version = (1, 3, 0)

    inc_base = os.path.join(self.apr_util_path, 'include')

    if os.path.isfile(os.path.join(inc_base, 'apr-1', 'apu_version.h')):
      inc_path = os.path.join(inc_base, 'apr-1')
    elif os.path.isfile(os.path.join(inc_base, 'apu_version.h')):
      inc_path = inc_base
    else:
      sys.stderr.write("ERROR: 'apu_version' not found.\n")
      sys.stderr.write("Use '--with-apr-util' option to configure APR-Util location.\n")
      sys.exit(1)

    version_file_path = os.path.join(inc_path, 'apu_version.h')
    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+APU_MAJOR_VERSION\s+(\d+)', txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+APU_MINOR_VERSION\s+(\d+)', txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+APU_PATCH_VERSION\s+(\d+)', txt, re.M)
    patch = int(vermatch.group(1))

    version = (major, minor, patch)
    self.aprutil_version = aprutil_version = '%d.%d.%d' % version

    if version < minimal_aprutil_version:
      sys.stderr.write("ERROR: apr-util %s or higher is required "
                       "(%s found)\n" % (
                          '.'.join(str(v) for v in minimal_aprutil_version),
                          aprutil_version))
      sys.exit(1)

    suffix = ''
    if major > 0:
        suffix = '-%d' % major

    defines = []
    if self.static_apr:
      lib_name = 'aprutil%s.lib' % suffix
      lib_dir = os.path.join(self.apr_util_path, 'LibR')
      dll_dir = None
      debug_dll_dir = None
      dll_name = None
      defines.extend(["APU_DECLARE_STATIC"])

      if not os.path.isdir(lib_dir) and \
         os.path.isfile(os.path.join(self.apr_util_path, 'lib', lib_name)):
        # Installed APR-Util instead of APR-Util-Source
        lib_dir = os.path.join(self.apr_util_path, 'lib')
        debug_lib_dir = None
      else:
        debug_lib_dir = os.path.join(self.apr_util_path, 'LibD')
    else:
      lib_name = 'libaprutil%s.lib' % suffix
      lib_dir = os.path.join(self.apr_util_path, 'Release')

      if not os.path.isdir(lib_dir) and \
         os.path.isfile(os.path.join(self.apr_util_path, 'lib', lib_name)):
        # Installed APR-Util instead of APR-Util-Source
        lib_dir = os.path.join(self.apr_util_path, 'lib')
        debug_lib_dir = lib_dir
      else:
        debug_lib_dir = os.path.join(self.apr_util_path, 'Debug')

      dll_name = 'libaprutil%s.dll' % suffix
      if os.path.isfile(os.path.join(lib_dir, dll_name)):
        dll_dir = lib_dir
        debug_dll_dir = debug_lib_dir
      else:
        dll_dir = os.path.join(self.apr_util_path, 'bin')
        debug_dll_dir = None

    extra_bin = []

    if dll_dir:
      bin_files = os.listdir(dll_dir)
      if debug_dll_dir and os.path.isdir(debug_dll_dir):
        debug_bin_files = os.listdir(debug_dll_dir)
      else:
        debug_bin_files = bin_files

      for bin in bin_files:
        if bin in debug_bin_files:
          if re.match('^(lib)?aprutil[-_].*' + suffix + '(d)?.dll$', bin):
            extra_bin.append(bin)

    self._libraries['aprutil'] = SVNCommonLibrary('apr-util', inc_path, lib_dir,
                                                   lib_name,
                                                   aprutil_version,
                                                   debug_lib_dir=debug_lib_dir,
                                                   dll_dir=dll_dir,
                                                   dll_name=dll_name,
                                                   debug_dll_dir=debug_dll_dir,
                                                   defines=defines,
                                                   extra_bin=extra_bin)

    # Perhaps apr-util can also provide memcached support
    if version >= (1, 3, 0) :
      self._libraries['apr_memcache'] = SVNCommonLibrary(
                                          'apr_memcache', inc_path, lib_dir,
                                          None, aprutil_version,
                                          defines=['SVN_HAVE_MEMCACHE'])

    # And now find expat
    # If we have apr-util as a source location, it is in a subdir.
    # If we have an install package it is in the lib subdir
    if os.path.exists(os.path.join(self.apr_util_path, 'xml/expat')):
      inc_path = os.path.join(self.apr_util_path, 'xml/expat/lib')
      lib_dir = os.path.join(self.apr_util_path, 'xml/expat/lib/LibR')
      debug_lib_dir = os.path.join(self.apr_util_path, 'xml/expat/lib/LibD')
    else:
      inc_path = os.path.join(self.apr_util_path, 'include')
      lib_dir = os.path.join(self.apr_util_path, 'lib')
      debug_lib_dir = None

    version_file_path = os.path.join(inc_path, 'expat.h')

    if not os.path.exists(version_file_path):
      sys.stderr.write("ERROR: '%s' not found.\n" % version_file_path);
      sys.stderr.write("Use '--with-apr-util' option to configure APR-Util's XML location.\n");
      sys.exit(1)

    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+XML_MAJOR_VERSION\s+(\d+)', txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+XML_MINOR_VERSION\s+(\d+)', txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+XML_MICRO_VERSION\s+(\d+)', txt, re.M)
    patch = int(vermatch.group(1))

    # apr-Util 0.9-1.4 compiled expat to 'xml.lib', but apr-util 1.5 switched
    # to the more common 'libexpat.lib'
    libname = 'libexpat.lib'
    if not os.path.exists(os.path.join(lib_dir, 'libexpat.lib')):
      libname = 'xml.lib'

    version = (major, minor, patch)
    xml_version = '%d.%d.%d' % version

    self._libraries['xml'] = SVNCommonLibrary('expat', inc_path, lib_dir,
                                               libname, xml_version,
                                               debug_lib_dir = debug_lib_dir,
                                               defines=['XML_STATIC'])

  def _find_httpd(self, show_warnings):
    "Find Apache HTTPD and version"

    minimal_httpd_version = (2, 2, 0)
    if not self.httpd_path:
      return

    inc_base = os.path.join(self.httpd_path, 'include')

    if os.path.isfile(os.path.join(inc_base, 'apache26', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache26')
    elif os.path.isfile(os.path.join(inc_base, 'apache24', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache24')
    elif os.path.isfile(os.path.join(inc_base, 'apache22', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache22')
    elif os.path.isfile(os.path.join(inc_base, 'apache20', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache20')
    elif os.path.isfile(os.path.join(inc_base, 'apache2', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache2')
    elif os.path.isfile(os.path.join(inc_base, 'apache', 'ap_release.h')):
      inc_path = os.path.join(inc_base, 'apache')
    elif os.path.isfile(os.path.join(inc_base, 'ap_release.h')):
      inc_path = inc_base
    else:
      if show_warnings:
        print('WARNING: \'ap_release.h\' not found')
        print("Use '--with-httpd' to configure openssl location.");
      return

    version_file_path = os.path.join(inc_path, 'ap_release.h')
    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+AP_SERVER_MAJORVERSION_NUMBER\s+(\d+)',
                         txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+AP_SERVER_MINORVERSION_NUMBER\s+(\d+)',
                         txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+AP_SERVER_PATCHLEVEL_NUMBER\s+(\d+)',
                         txt, re.M)
    patch = int(vermatch.group(1))

    version = (major, minor, patch)
    httpd_version = '%d.%d.%d' % version

    if version < minimal_httpd_version:
      if show_warnings:
        print("WARNING: httpd %s or higher is required "
                        "(%s found)\n" % (
                          '.'.join(str(v) for v in minimal_httpd_version),
                          httpd_version))
      return

    lib_name = 'libhttpd.lib'
    lib_base = self.httpd_path

    debug_lib_dir = None

    if os.path.isfile(os.path.join(lib_base, 'lib', lib_name)):
      # Install location
      lib_dir = os.path.join(lib_base, 'lib')
    elif os.path.isfile(os.path.join(lib_base, 'Release', lib_name)):
      # Source location
      lib_dir = os.path.join(lib_base, 'Release')
      if os.path.isfile(os.path.join(lib_base, 'Debug', lib_name)):
        debug_lib_dir = os.path.join(lib_base, 'Debug')

    # Our modules run inside httpd, so we don't have to find binaries

    self._libraries['httpd'] = SVNCommonLibrary('httpd', inc_path, lib_dir, lib_name,
                                                httpd_version,
                                                debug_lib_dir=debug_lib_dir,
                                                defines=['AP_DECLARE_EXPORT'])

    # And now find mod_dav

    if os.path.isfile(os.path.join(inc_path, 'mod_dav.h')):
      # Install location, we are lucky
      inc_path = inc_path
    elif os.path.isfile(os.path.join(lib_base, 'modules/dav/main/mod_dav.h')):
      # Source location
      inc_path = os.path.join(lib_base, 'modules/dav/main')
    else:
      if show_warnings:
        print("WARNING: Can't find mod_dav.h in the httpd directory")
        return

    lib_name = 'mod_dav.lib'
    if os.path.isfile(os.path.join(lib_dir, lib_name)):
      # Same location as httpd
      lib_dir = lib_dir
    elif os.path.isfile(os.path.join(lib_base, 'modules/dav/main/Release', lib_name)):
      # Source location
      lib_dir = os.path.join(lib_base, 'modules/dav/main/Release')

      if os.path.isfile(os.path.join(lib_base, 'modules/dav/main/Debug', lib_name)):
        debug_lib_dir = os.path.join(lib_base, 'modules/dav/main/Debug')
      else:
        debug_lib_dir = None
    else:
      if show_warnings:
        print("WARNING: Can't find mod_dav.lib in the httpd directory")
        return

    self._libraries['mod_dav'] = SVNCommonLibrary('mod_dav', inc_path, lib_dir, lib_name,
                                                  httpd_version,
                                                  debug_lib_dir=debug_lib_dir)

  def _find_zlib(self):
    "Find the ZLib library and version"

    minimal_zlib_version = (1, 2, 5)

    if not self.zlib_path or not os.path.isdir(self.zlib_path):
      sys.stderr.write("ERROR: '%s' not found.\n" % self.zlib_path);
      sys.stderr.write("Use '--with-zlib' option to configure ZLib location.\n");
      sys.exit(1)

    if os.path.isdir(os.path.join(self.zlib_path, 'include')):
      # We have an install location
      inc_path = os.path.join(self.zlib_path, 'include')
      lib_path = os.path.join(self.zlib_path, 'lib')

      # Different build options produce different library names :(
      if os.path.exists(os.path.join(lib_path, 'zlibstatic.lib')):
        # CMake default: zlibstatic.lib (static) and zlib.lib (dll)
        lib_name = 'zlibstatic.lib'
      elif os.path.exists(os.path.join(lib_path, 'zlibstat.lib')):
        # Visual Studio project file default: zlibstat.lib (static) and zlibwapi.lib (dll)
        lib_name = 'zlibstat.lib'
      else:
        # Standard makefile produces zlib.lib (static) and zdll.lib (dll)
        lib_name = 'zlib.lib'
      debug_lib_name = None
    else:
      # We have a source location
      inc_path = lib_path = self.zlib_path
      lib_name = 'zlibstat.lib'
      debug_lib_name = 'zlibstatD.lib'

    version_file_path = os.path.join(inc_path, 'zlib.h')

    if not os.path.exists(version_file_path):
      sys.stderr.write("ERROR: '%s' not found.\n" % version_file_path);
      sys.stderr.write("Use '--with-zlib' option to configure ZLib location.\n");
      sys.exit(1)

    txt = open(version_file_path).read()
    vermatch = re.search(
                r'^\s*#define\s+ZLIB_VERSION\s+"(\d+(?:\.\d+){1,3})(?:-\w+)?"',
                 txt, re.M)

    version = tuple(map(int, vermatch.group(1).split('.')))
    self.zlib_version = '.'.join(map(str, version))

    if version < minimal_zlib_version:
      sys.stderr.write("ERROR: ZLib %s or higher is required "
                       "(%s found)\n" % (
                          '.'.join(str(v) for v in minimal_zlib_version),
                          self.zlib_version))
      sys.exit(1)

    self._libraries['zlib'] = SVNCommonLibrary('zlib', inc_path, lib_path, lib_name,
                                                self.zlib_version,
                                                debug_lib_name=debug_lib_name)

  def _find_bdb(self, show_warnings):
    "Find the Berkeley DB library and version"

    # try default path to detect BDB support, unless different path is
    # specified so to keep pre 1.10-behavior for BDB detection on Windows
    bdb_path = 'db4-win32'

    if self.bdb_path:
      bdb_path = self.bdb_path

    inc_path = os.path.join(bdb_path, 'include')
    db_h_path = os.path.join(inc_path, 'db.h')

    if not os.path.isfile(db_h_path):
      if show_warnings and self.bdb_path:
        print('WARNING: \'%s\' not found' % (db_h_path,))
        print("Use '--with-berkeley-db' to configure BDB location.");
      return

    # Obtain bdb version from db.h
    txt = open(db_h_path).read()

    maj_match = re.search(r'DB_VERSION_MAJOR\s+(\d+)', txt)
    min_match = re.search(r'DB_VERSION_MINOR\s+(\d+)', txt)
    patch_match = re.search(r'DB_VERSION_PATCH\s+(\d+)', txt)

    if maj_match and min_match and patch_match:
      ver = (int(maj_match.group(1)),
             int(min_match.group(1)),
             int(patch_match.group(1)))
    else:
      return

    version = '%d.%d.%d' % ver
    versuffix = '%d%d' % (ver[0], ver[1])

    # Before adding "60" to this list, see build/ac-macros/berkeley-db.m4.
    if versuffix not in (
            '50', '51', '52', '53',
            '40', '41', '42', '43', '44', '45', '46', '47', '48',
       ):
      return

    lib_dir = os.path.join(bdb_path, 'lib')
    lib_name = 'libdb%s.lib' % (versuffix,)

    if not os.path.exists(os.path.join(lib_dir, lib_name)):
      return

    # Do we have a debug version?
    debug_lib_name = 'libdb%sd.lib' % (versuffix,)
    if not os.path.isfile(os.path.join(lib_dir, debug_lib_name)):
      debug_lib_name = None

    dll_dir = os.path.join(bdb_path, 'bin')

    # Are there binaries we should copy for testing?
    dll_name = os.path.splitext(lib_name)[0] + '.dll'
    if not os.path.isfile(os.path.join(dll_dir, dll_name)):
      dll_name = None

    if debug_lib_name:
      debug_dll_name = os.path.splitext(debug_lib_name)[0] + '.dll'
      if not os.path.isfile(os.path.join(dll_dir, debug_dll_name)):
        debug_dll_name = None
    else:
      debug_dll_name = None

    # Usually apr-util doesn't find BDB on Windows, so we help apr-util
    # by defining the value ourselves (Legacy behavior)
    defines = ['APU_HAVE_DB=1', 'SVN_LIBSVN_FS_LINKS_FS_BASE']

    self._libraries['db'] = SVNCommonLibrary('db', inc_path, lib_dir, lib_name,
                                              version,
                                              debug_lib_name=debug_lib_name,
                                              dll_dir=dll_dir,
                                              dll_name=dll_name,
                                              debug_dll_name=debug_dll_name,
                                              defines=defines)

  def _find_openssl(self, show_warnings):
    "Find openssl"

    if not self.openssl_path:
      return

    version_path = os.path.join(self.openssl_path, 'inc32/openssl/opensslv.h')
    if os.path.isfile(version_path):
      # We have an OpenSSL Source location
      # For legacy reason
      inc_dir = os.path.join(self.openssl_path, 'inc32')
      if self.static_openssl:
        lib_dir = os.path.join(self.openssl_path, 'out32')
        bin_dir = None
      else:
        lib_dir = os.path.join(self.openssl_path, 'out32dll')
        bin_dir = lib_dir
    elif os.path.isfile(os.path.join(self.openssl_path,
                        'include/openssl/opensslv.h')):
      version_path = os.path.join(self.openssl_path,
                                  'include/openssl/opensslv.h')
      inc_dir = os.path.join(self.openssl_path, 'include')
      lib_dir = os.path.join(self.openssl_path, 'lib')
      if self.static_openssl:
        bin_dir = None
      else:
        bin_dir = os.path.join(self.openssl_path, 'bin')
    else:
      if show_warnings:
        print('WARNING: \'opensslv.h\' not found')
        print("Use '--with-openssl' to configure openssl location.");
      return

    txt = open(version_path).read()

    vermatch = re.search(
      r'#\s*define\s+OPENSSL_VERSION_TEXT\s+"OpenSSL\s+((\d+)\.(\d+).(\d+)([^ -]*))',
      txt)

    version = (int(vermatch.group(2)),
               int(vermatch.group(3)),
               int(vermatch.group(4)))
    openssl_version = vermatch.group(1)

    libcrypto = 'libcrypto'
    libssl = 'libssl'
    versuffix = '-%d_%d' % version[0:2]
    if version < (1, 1, 0):
      libcrypto = 'libeay32'
      libssl = 'ssleay32'
      versuffix = ''

    self._libraries['openssl'] = SVNCommonLibrary('openssl', inc_dir, lib_dir,
                                                  '%s.lib' % (libssl,),
                                                  openssl_version,
                                                  dll_name='%s%s.dll' %
                                                      (libssl, versuffix),
                                                  dll_dir=bin_dir)

    self._libraries['libcrypto'] = SVNCommonLibrary('openssl', inc_dir, lib_dir,
                                                    '%s.lib' % (libcrypto,),
                                                    openssl_version,
                                                    dll_name='%s%s.dll' %
                                                      (libcrypto, versuffix),
                                                    dll_dir=bin_dir)

  def _find_perl(self, show_warnings):
    "Find the right perl library name to link swig bindings with"

    try:
      subprocess.run(['perl', '-v'], capture_output=True)
    except OSError:
      return  # not found, permission error, ...

    fp = os.popen('perl -MConfig -e ' + escape_shell_arg(
                  'print "$Config{libperl}\\n"; '
                  'print "$Config{PERL_REVISION}.$Config{PERL_VERSION}.'
                          '$Config{PERL_SUBVERSION}\\n"; '
                  'print "$Config{archlib}\\n"'), 'r')
    try:
      line = fp.readline()
      if line:
        perl_lib = line.strip()
      else:
        return

      line = fp.readline()
      if line:
        perl_version = line.strip()
        perl_ver = perl_version.split('.')
      else:
        return

      line = fp.readline()
      if line:
        lib_dir = os.path.join(line.strip(), 'CORE')
        inc_dir = lib_dir
    finally:
      fp.close()

    perl_ver = tuple(map(int, perl_ver))
    forced_includes = []

    if perl_ver >= (5, 18, 0):
      forced_includes.append('swigutil_pl__pre_perl.h')

    self._libraries['perl'] = SVNCommonLibrary('perl', inc_dir, lib_dir,
                                               perl_lib, perl_version,
                                               forced_includes=forced_includes)

  def _find_ruby(self, show_warnings):
    "Find the right Ruby library name to link swig bindings with"

    try:
      subprocess.run(['ruby', '--version'], capture_output=True)
    except OSError:
      return  # not found, permission error, ...

    lib_dir = None
    inc_dirs = []

    # Pass -W0 to stifle the "-e:1: Use RbConfig instead of obsolete
    # and deprecated Config." warning if we are using Ruby 1.9.
    fp = os.popen('ruby -rrbconfig -W0 -e ' + escape_shell_arg(
                  "puts RbConfig::CONFIG['ruby_version'];"
                  "puts RbConfig::CONFIG['LIBRUBY'];"
                  "puts RbConfig::CONFIG['libdir'];"
                  "puts RbConfig::CONFIG['rubyhdrdir'];"
                  "puts RbConfig::CONFIG['arch'];"), 'r')
    try:
      line = fp.readline()
      if line:
        ruby_version = line.strip()

      line = fp.readline()
      if line:
        ruby_lib = line.strip()

      line = fp.readline()
      if line:
        lib_dir = line.strip()

      line = fp.readline()
      if line:
        inc_base = line.strip()
        inc_dirs = [inc_base]

      line = fp.readline()
      if line:
        inc_dirs.append(os.path.join(inc_base, line.strip()))

    finally:
      fp.close()

    if not lib_dir:
      return

    # Visual C++ prior to VS2015 doesn't have a standard compliant snprintf
    if self.vs_version < '2015':
      defines = ['snprintf=_snprintf']
    else:
      defines = []

    ver = ruby_version.split('.')
    ver = tuple(map(int, ver))
    if ver >= (1, 8, 0):
      defines.extend(["HAVE_RB_ERRINFO"])

    forced_includes = []

    if ver >= (1, 8, 0):
      # Swig redefines NUM2LL as NUM2LONG if it isn't defined, but on Windows
      # ruby 1.8+ declares NUM2LL as a static inline function.
      # (LL2NUM and NUM2ULL don't have these problems)
      defines.extend(['NUM2LL=NUM2LL'])

    if ver >= (1, 9, 0):
      forced_includes.append('swigutil_rb__pre_ruby.h')
      defines.extend(["SVN_SWIG_RUBY__CUSTOM_RUBY_CONFIG"])

    self._libraries['ruby'] = SVNCommonLibrary('ruby', inc_dirs, lib_dir,
                                               ruby_lib, ruby_version,
                                               defines=defines,
                                               forced_includes=forced_includes)

  def _find_python(self, show_warnings):
    "Find the appropriate options for creating SWIG-based Python modules"

    try:
      import sysconfig
    except ImportError:
      return
    config_vars = sysconfig.get_config_vars()
    inc_dir = config_vars['INCLUDEPY']
    base_dir = config_vars.get('installed_base') or config_vars.get('base')
    lib_dir = os.path.join(base_dir, 'libs')

    if sys.version_info[0] >= 3:
      if self.swig_version < (3, 0, 10):
        if show_warnings:
          print("WARNING: Subversion Python bindings for Python 3 require SWIG 3.0.10 or newer")
        return
      if self.swig_version < (4, 0, 0):
        opts = "-python -py3 -nofastunpack -modern"
      elif self.swig_version < (4, 1, 0):
        opts = "-python -py3 -nofastunpack"
      else:
        opts = "-python -nofastunpack"
      if show_warnings and self.swig_version > (4, 0, 2):
        print("WARNING: Subversion Python bindings may work,\n"
              "but we didn't check with this SWIG version.")
    else:
      if not ((1, 3, 24) <= self.swig_version < (4, 0, 0)):
        if show_warnings:
          print("WARNING: Subversion Python bindings for Python 2 require 1.3.24 <= SWIG < 4.0.0")
        return
      opts = "-python -classic"

    self.user_macros.append(UserMacro("SWIG_PY_OPTS", opts))
    self._libraries['python'] = SVNCommonLibrary('python', inc_dir, lib_dir, None,
                                                 sys.version.split(' ')[0])

  def _find_py3c(self, show_warnings):
    "Find the py3c library which is used in SWIG python bindings"
    show_warnings = True
    # Assume a default path, unless otherwise specified
    py3c_path = "py3c"

    if self.py3c_path:
      py3c_path = self.py3c_path

    py3c_path = os.path.abspath(py3c_path)
    inc_path = os.path.join(py3c_path, 'include')
    py3c_hdr_path = os.path.join(inc_path, 'py3c.h')

    pc_path = os.path.join(py3c_path, 'py3c.pc.in')

    if not os.path.isfile(py3c_hdr_path):
      if show_warnings:
        print('WARNING: "%s" not found' % py3c_hdr_path)
        print('Use "--with-py3c" to configure py3c location.')
      return False

    with open(pc_path) as fp:
      txt = fp.read()

    ver_match = re.search(r'Version:\s+([0-9.]+)', txt)

    if not ver_match:
      if show_warnings:
        print("WARNING: Failed to find version in '%s'" % pc_path)
      return False

    py3c_version = ver_match.group(1)

    self._libraries['py3c'] = SVNCommonLibrary('py3c', inc_path, None,
                                               None, py3c_version)

    return True

  def _find_jdk(self, show_warnings):
    "Find details about an installed jdk"

    jdk_path = self.jdk_path
    self.jdk_path = None # No jdk on errors

    minimal_jdk_version = (1, 0, 0) # ### Provide sane default

    if not jdk_path:
      jdk_ver = None
      try:
        try:
          # Python >=3.0
          import winreg
        except ImportError:
          # Python <3.0
          import _winreg as winreg
        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                           r"SOFTWARE\JavaSoft\Java Development Kit")
        # Find the newest JDK version.
        num_values = winreg.QueryInfoKey(key)[1]
        for i in range(num_values):
          (name, value, key_type) = winreg.EnumValue(key, i)
          if name == "CurrentVersion":
            jdk_ver = value
            break

        # Find the JDK path.
        if jdk_ver is not None:
          key = winreg.OpenKey(key, jdk_ver)
          num_values = winreg.QueryInfoKey(key)[1]
          for i in range(num_values):
            (name, value, key_type) = winreg.EnumValue(key, i)
            if name == "JavaHome":
              jdk_path = value
              break
        winreg.CloseKey(key)
      except (ImportError, EnvironmentError):
        pass

    if not jdk_path or not os.path.isdir(jdk_path):
      return

    try:
      # Apparently a 1.8 javac writes its version output to stderr, while
      # a 1.10 javac writes it to stdout. To catch them all, we redirect
      # stderr to stdout.
      outfp = subprocess.Popen([os.path.join(jdk_path, 'bin', 'javac.exe'),
                               '-version'], stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT).stdout
      line = outfp.read().decode('utf8')
      if line:
        vermatch = re.search(r'(([0-9]+(\.[0-9]+)+)(_[._0-9]+)?)', line, re.M)
      else:
        vermatch = None

      if vermatch:
        version = tuple(map(int, vermatch.groups()[1].split('.')))
        versionstr = vermatch.groups()[0]
      else:
        if show_warnings:
          print('Could not find installed JDK,')
        return
      outfp.close()
    except OSError:
      if show_warnings:
        print('Could not find installed JDK,')
      return

    if version < minimal_jdk_version:
      if show_warnings:
        print('Found java jdk %s, but >= %s is required. '
              'javahl will not be built.\n' % \
              (versionstr, '.'.join(str(v) for v in minimal_jdk_version)))
      return

    self.jdk_path = jdk_path
    inc_dirs = [
        os.path.join(jdk_path, 'include'),
        os.path.join(jdk_path, 'include', 'win32'),
      ]

    lib_dir = os.path.join(jdk_path, 'lib')

    # The JDK provides .lib files, but we currently don't use these.
    self._libraries['java_sdk'] = SVNCommonLibrary('java-sdk', inc_dirs,
                                                   lib_dir, None,
                                                   versionstr)

  def _find_swig(self, show_warnings):
    "Find details about an installed swig"

    minimal_swig_version = (1, 3, 25)

    if not self.swig_path:
      swig_exe = 'swig.exe'
    else:
      swig_exe = os.path.abspath(os.path.join(self.swig_path, 'swig.exe'))

    if self.swig_path is not None:
      self.swig_exe = os.path.abspath(os.path.join(self.swig_path, 'swig.exe'))
    else:
      self.swig_exe = 'swig'

    swig_version = None
    try:
      fp = subprocess.Popen([self.swig_exe, '-version'],
                            stdout=subprocess.PIPE).stdout
      txt = fp.read().decode('utf8')
      if txt:
        vermatch = re.search(r'^SWIG\ Version\ (\d+)\.(\d+)\.(\d+)', txt, re.M)
      else:
        vermatch = None

      if vermatch:
        swig_version = tuple(map(int, vermatch.groups()))
      fp.close()
    except OSError:
      swig_version = None

    if not swig_version:
      if show_warnings:
        print('Could not find installed SWIG')
      return False

    swig_ver = '%d.%d.%d' % (swig_version)
    if swig_version < minimal_swig_version:
      if show_warnings:
        print('Found swig %s, but >= %s is required. '
              'the swig bindings will not be built.\n' %
              (swig_version, '.'.join(str(v) for v in minimal_swig_version)))
      return

    try:
      fp = subprocess.Popen([self.swig_exe, '-swiglib'],
                            stdout=subprocess.PIPE).stdout
      lib_dir = fp.readline().decode('utf8').strip()
      fp.close()
    except OSError:
      lib_dir = None
      fp.close()

    if not lib_dir:
      if show_warnings:
        print('Could not find libdir of installed SWIG')
      return False

    if (not self.swig_path and
        os.path.isfile(os.path.join(lib_dir, '../swig.exe'))):
      self.swig_path = os.path.dirname(lib_dir)

    inc_dirs = [
        'subversion/bindings/swig',
        'subversion/bindings/swig/proxy',
        'subversion/bindings/swig/include',
      ]

    self.swig_libdir = lib_dir
    self.swig_version = swig_version

    self._libraries['swig'] = SVNCommonLibrary('swig', inc_dirs, lib_dir, None,
                                               swig_ver)
    return True

  def _get_serf_version(self, inc_dir):
    "Retrieves the serf version from serf.h"

    # shouldn't be called unless serf is there
    assert inc_dir and os.path.exists(inc_dir)

    serf_ver_maj = None
    serf_ver_min = None
    serf_ver_patch = None

    # serf.h should be present
    if not os.path.exists(os.path.join(inc_dir, 'serf.h')):
      return None, None, None

    txt = open(os.path.join(inc_dir, 'serf.h')).read()

    maj_match = re.search(r'SERF_MAJOR_VERSION\s+(\d+)', txt)
    min_match = re.search(r'SERF_MINOR_VERSION\s+(\d+)', txt)
    patch_match = re.search(r'SERF_PATCH_VERSION\s+(\d+)', txt)
    if maj_match:
      serf_ver_maj = int(maj_match.group(1))
    if min_match:
      serf_ver_min = int(min_match.group(1))
    if patch_match:
      serf_ver_patch = int(patch_match.group(1))

    return serf_ver_maj, serf_ver_min, serf_ver_patch

  def _find_serf(self, show_warnings):
    "Check if serf and its dependencies are available"

    minimal_serf_version = (1, 3, 4)

    if not self.serf_path:
      return

    inc_dir = self.serf_path

    if os.path.isfile(os.path.join(inc_dir, 'serf.h')):
      # Source layout
      lib_dir = self.serf_path
      debug_lib_dir = None
      inc_dir = self.serf_path
    elif os.path.isfile(os.path.join(self.serf_path, 'include/serf-1/serf.h')):
      # Install layout
      inc_dir = os.path.join(self.serf_path, 'include/serf-1')
      lib_dir = os.path.join(self.serf_path, 'lib')
      debug_lib_dir = None
    elif os.path.isfile(os.path.join(self.serf_path, 'include/serf-2/serf.h')):
      # Install layout
      inc_dir = os.path.join(self.serf_path, 'include/serf-2')
      lib_dir = os.path.join(self.serf_path, 'lib')
      debug_lib_dir = None
    else:
      if show_warnings:
        print('WARNING: \'serf.h\' not found')
        print("Use '--with-serf' to configure serf location.");
      return

    version = self._get_serf_version(inc_dir)
    serf_version = '.'.join(str(v) for v in version)

    if version < minimal_serf_version:
      if show_warnings:
        print('Found serf %s, but >= %s is required. '
              'ra_serf will not be built.\n' %
              (serf_version, '.'.join(str(v) for v in minimal_serf_version)))
      return

    serf_ver_maj = version[0]

    if serf_ver_maj > 0:
      lib_name = 'serf-%d.lib' % (serf_ver_maj,)
    else:
      lib_name = 'serf.lib'

    defines = ['SVN_HAVE_SERF', 'SVN_LIBSVN_RA_LINKS_RA_SERF']

    self._libraries['serf'] = SVNCommonLibrary('serf', inc_dir, lib_dir,
                                                lib_name, serf_version,
                                                debug_lib_dir=debug_lib_dir,
                                                defines=defines)

  def _find_sasl(self, show_warnings):
    "Check if sals is available"

    minimal_sasl_version = (2, 0, 0)

    if not self.sasl_path:
      return

    inc_dir = os.path.join(self.sasl_path, 'include')

    version_file_path = os.path.join(inc_dir, 'sasl.h')

    if not os.path.isfile(version_file_path):
      if show_warnings:
        print('WARNING: \'%s\' not found' % (version_file_path,))
        print("Use '--with-sasl' to configure sasl location.");
      return

    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MAJOR\s+(\d+)', txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MINOR\s+(\d+)', txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_STEP\s+(\d+)', txt, re.M)
    patch = int(vermatch.group(1))

    version = (major, minor, patch)
    sasl_version = '.'.join(str(v) for v in version)

    if version < minimal_sasl_version:
      if show_warnings:
        print('Found sasl %s, but >= %s is required. '
              'sals support will not be built.\n' %
              (sasl_version, '.'.join(str(v) for v in minimal_serf_version)))
      return

    lib_dir = os.path.join(self.sasl_path, 'lib')

    if os.path.isfile(os.path.join(lib_dir, 'libsasl.dll')):
      dll_dir = lib_dir
      dll_name = 'libsasl.dll'
    elif os.path.isfile(os.path.join(self.sasl_path, 'bin', 'libsasl.dll')):
      dll_dir = os.path.join(self.sasl_path, 'bin')
      dll_name = 'libsasl.dll'
    else:
      # Probably a static compilation
      dll_dir = None
      dll_name = None

    self._libraries['sasl'] = SVNCommonLibrary('sasl', inc_dir, lib_dir,
                                               'libsasl.lib', sasl_version,
                                               dll_dir=dll_dir,
                                               dll_name=dll_name,
                                               defines=['SVN_HAVE_SASL'])

  def _find_libintl(self, show_warnings):
    "Find gettext support"
    minimal_libintl_version = (0, 14, 1)

    if not self.enable_nls or not self.libintl_path:
      return;

    # We support 2 scenarios.
    if os.path.isfile(os.path.join(self.libintl_path, 'inc', 'libintl.h')) and\
       os.path.isfile(os.path.join(self.libintl_path, 'lib', 'intl3_svn.lib')):

      # 1. Subversion's custom libintl based on gettext 0.14.1
      inc_dir = os.path.join(self.libintl_path, 'inc')
      lib_dir = os.path.join(self.libintl_path, 'lib')
      dll_dir = os.path.join(self.libintl_path, 'bin')

      lib_name = 'intl3_svn.lib'
      dll_name = 'intl3_svn.dll'
    elif os.path.isfile(os.path.join(self.libintl_path, \
                                     'include', 'libintl.h')):
      # 2. A gettext install
      inc_dir = os.path.join(self.libintl_path, 'include')
      lib_dir = os.path.join(self.libintl_path, 'lib')
      dll_dir = os.path.join(self.libintl_path, 'bin')

      lib_name = 'intl.lib'
      dll_name = 'intl.dll'
    else:
      if (show_warnings):
        print('WARNING: \'libintl.h\' not found')
        print("Use '--with-libintl' to configure libintl location.")
      return

    version_file_path = os.path.join(inc_dir, 'libintl.h')
    txt = open(version_file_path).read()

    match = re.search(r'^\s*#define\s+LIBINTL_VERSION\s+((0x)?[0-9A-Fa-f]+)',
                      txt, re.M)

    ver = int(match.group(1), 0)
    version = (ver >> 16, (ver >> 8) & 0xFF, ver & 0xFF)

    libintl_version = '.'.join(str(v) for v in version)

    if version < minimal_libintl_version:
      if show_warnings:
        print('Found libintl %s, but >= %s is required.\n' % \
              (libintl_version,
               '.'.join(str(v) for v in minimal_libintl_version)))
      return

    self._libraries['intl'] = SVNCommonLibrary('libintl', inc_dir, lib_dir,
                                               lib_name, libintl_version,
                                               dll_dir=dll_dir,
                                               dll_name=dll_name)

  def _find_sqlite(self, show_warnings):
    "Find the Sqlite library and version"

    minimal_sqlite_version = (3, 8, 2)

    # For SQLite we support 3 scenarios:
    # - Installed in standard directory layout
    # - Installed in legacy directory layout
    # - Amalgamation compiled directly into our libraries

    sqlite_base = self.sqlite_path

    lib_dir = None
    dll_dir = None
    dll_name = None
    defines = []

    lib_name = 'sqlite3.lib'

    if os.path.isfile(os.path.join(sqlite_base, 'include/sqlite3.h')):
      # Standard layout
      inc_dir = os.path.join(sqlite_base, 'include')
      lib_dir = os.path.join(sqlite_base, 'lib')

      # We assume a static library, but let's support shared in this case
      if os.path.isfile(os.path.join(sqlite_base, 'bin/sqlite3.dll')):
        dll_dir = os.path.join(sqlite_base, 'bin')
        dll_name = 'sqlite3.dll'
    elif os.path.isfile(os.path.join(sqlite_base, 'inc/sqlite3.h')):
      # Standard layout
      inc_dir = os.path.join(sqlite_base, 'inc')
      lib_dir = os.path.join(sqlite_base, 'lib')

      # We assume a static library, but let's support shared in this case
      if os.path.isfile(os.path.join(sqlite_base, 'bin/sqlite3.dll')):
        dll_dir = os.path.join(sqlite_base, 'bin')
        dll_name = 'sqlite3.dll'
    elif (os.path.isfile(os.path.join(sqlite_base, 'sqlite3.h'))
          and os.path.isfile(os.path.join(sqlite_base, 'sqlite3.c'))):
      # Amalgamation
      inc_dir = sqlite_base
      lib_dir = None
      lib_name = None
      defines.append('SVN_SQLITE_INLINE')
    else:
      sys.stderr.write("ERROR: SQLite not found\n")
      sys.stderr.write("Use '--with-sqlite' option to configure sqlite location.\n");
      sys.exit(1)

    version_file_path = os.path.join(inc_dir, 'sqlite3.h')

    txt = open(version_file_path).read()

    match = re.search(r'^\s*#define\s+SQLITE_VERSION\s+'
                      r'"(\d+)\.(\d+)\.(\d+)(?:\.(\d))?"', txt, re.M)

    version = match.groups()

    # Sqlite doesn't add patch numbers for their ordinary releases
    if not version[3]:
      version = version[0:3]

    version = tuple(map(int, version))

    sqlite_version = '.'.join(str(v) for v in version)

    if version < minimal_sqlite_version:
      sys.stderr.write("ERROR: sqlite %s or higher is required "
                       "(%s found)\n" % (
                          '.'.join(str(v) for v in minimal_sqlite_version),
                          sqlite_version))
      sys.exit(1)

    self._libraries['sqlite'] = SVNCommonLibrary('sqlite', inc_dir, lib_dir,
                                                 lib_name, sqlite_version,
                                                 dll_dir=dll_dir,
                                                 dll_name=dll_name,
                                                 defines=defines)

  def _find_lz4(self):
    "Find the LZ4 library"

    # For now, we always use the internal (bundled) library.
    version_file_path = os.path.join('subversion', 'libsvn_subr',
                                     'lz4', 'lz4internal.h')
    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MAJOR\s+(\d+)',
                         txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MINOR\s+(\d+)',
                         txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_RELEASE\s+(\d+)',
                         txt, re.M)
    rel = vermatch.group(1)

    lz4_version = '%d.%d.%s' % (major, minor, rel)
    self._libraries['lz4'] = SVNCommonLibrary('lz4', None, None, None,
                                              lz4_version, internal=True,
                                              defines=['SVN_INTERNAL_LZ4'])

  def _find_utf8proc(self):
    "Find the Utf8proc library"

    # For now, we always use the internal (bundled) library.
    version_file_path = os.path.join('subversion', 'libsvn_subr',
                                     'utf8proc', 'utf8proc_internal.h')
    txt = open(version_file_path).read()

    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MAJOR\s+(\d+)',
                         txt, re.M)
    major = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MINOR\s+(\d+)',
                         txt, re.M)
    minor = int(vermatch.group(1))

    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_PATCH\s+(\d+)',
                         txt, re.M)
    patch = int(vermatch.group(1))

    utf8proc_version = '%d.%d.%d' % (major, minor, patch)
    self._libraries['utf8proc'] = SVNCommonLibrary('utf8proc', None, None,
                                                   None, utf8proc_version,
                                                   internal=True,
                                        defines=['SVN_INTERNAL_UTF8PROC'])

# ============================================================================
# This is a cut-down and modified version of code from:
#   subversion/subversion/bindings/swig/python/svn/core.py
#
if sys.platform == "win32":
  _escape_shell_arg_re = re.compile(r'(\\+)(\"|$)')

  def escape_shell_arg(arg):
    # The (very strange) parsing rules used by the C runtime library are
    # described at:
    # http://msdn.microsoft.com/library/en-us/vclang/html/_pluslang_Parsing_C.2b2b_.Command.2d.Line_Arguments.asp

    # double up slashes, but only if they are followed by a quote character
    arg = re.sub(_escape_shell_arg_re, r'\1\1\2', arg)

    # surround by quotes and escape quotes inside
    arg = '"' + arg.replace('"', '"^""') + '"'
    return arg

else:
  def escape_shell_arg(str):
    return "'" + str.replace("'", "'\\''") + "'"
