1#
2#
3# Licensed to the Apache Software Foundation (ASF) under one
4# or more contributor license agreements.  See the NOTICE file
5# distributed with this work for additional information
6# regarding copyright ownership.  The ASF licenses this file
7# to you under the Apache License, Version 2.0 (the
8# "License"); you may not use this file except in compliance
9# with the License.  You may obtain a copy of the License at
10#
11#   http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing,
14# software distributed under the License is distributed on an
15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16# KIND, either express or implied.  See the License for the
17# specific language governing permissions and limitations
18# under the License.
19#
20#
21#
22# gen_win_dependencies.py
23#
24#   base class for generating windows projects, containing the
25#   dependency locator code shared between the test runner and
26#   the make file generators
27#
28
29import os
30import sys
31import fnmatch
32import re
33import subprocess
34import string
35from collections import namedtuple
36
37if sys.version_info[0] >= 3:
38  # Python >=3.0
39  from io import StringIO
40else:
41  # Python <3.0
42  try:
43    from cStringIO import StringIO
44  except ImportError:
45    from StringIO import StringIO
46
47import gen_base
48import ezt
49
50UserMacro = namedtuple('UserMacro', ['name', 'value'])
51
52class SVNCommonLibrary:
53
54  def __init__(self, name, include_dirs, lib_dir, lib_name, version=None,
55               debug_lib_dir=None, debug_lib_name=None, dll_dir=None,
56               dll_name=None, debug_dll_dir=None, debug_dll_name=None,
57               defines=[], forced_includes=[], extra_bin=[], internal=False):
58    self.name = name
59    if include_dirs:
60      self.include_dirs = include_dirs if isinstance(include_dirs, list) \
61                                       else [include_dirs]
62    else:
63      self.include_dirs = []
64    self.defines = defines if not defines or isinstance(defines, list) else [defines]
65    self.lib_dir = lib_dir
66    self.lib_name = lib_name
67    self.version = version
68    self.dll_dir = dll_dir
69    self.dll_name = dll_name
70
71    self.forced_includes = forced_includes if not forced_includes \
72                                           or isinstance(forced_includes, list) \
73                                           else [forced_includes]
74
75    if debug_lib_dir:
76      self.debug_lib_dir = debug_lib_dir
77    else:
78      self.debug_lib_dir = lib_dir
79
80    if debug_lib_name:
81      self.debug_lib_name = debug_lib_name
82    else:
83      self.debug_lib_name = lib_name
84
85    if debug_dll_dir:
86      self.debug_dll_dir = debug_dll_dir
87    else:
88      self.debug_dll_dir = dll_dir
89
90    if debug_dll_name:
91      self.debug_dll_name = debug_dll_name
92    else:
93      self.debug_dll_name = dll_name
94
95    self.extra_bin = extra_bin
96    self.internal = internal
97
98class GenDependenciesBase(gen_base.GeneratorBase):
99  """This intermediate base class exists to be instantiated by win-tests.py,
100  in order to obtain information from build.conf and library paths without
101  actually doing any generation."""
102  _extension_map = {
103    ('exe', 'target'): '.exe',
104    ('exe', 'object'): '.obj',
105    ('lib', 'target'): '.dll',
106    ('lib', 'object'): '.obj',
107    ('pyd', 'target'): '.pyd',
108    ('pyd', 'object'): '.obj',
109    ('so', 'target'): '.so',
110    ('so', 'object'): '.obj',
111    }
112
113  _libraries = {}     # Dict of SVNCommonLibrary instances of found libraries
114
115  _optional_libraries = [  # List of optional libraries to suppress warnings
116        'db',
117        'intl',
118        'serf',
119        'sasl',
120        'swig',
121        'perl',
122        'python',
123        'py3c',
124        'ruby',
125        'java_sdk',
126        'openssl',
127        'apr_memcache',
128
129        # So optional, we don't even have any code to detect them on Windows
130        'magic',
131        'macos-plist',
132        'macos-keychain',
133  ]
134
135  # When build.conf contains a 'when = SOMETHING' where SOMETHING is not in
136  # this list, then the project is not generated on Windows.
137  _windows_when = [
138     'INSTALL_APACHE_MODS',
139     # not 'SVN_USE_GMOCK',
140  ]
141
142  def parse_options(self, options):
143    self.apr_path = 'apr'
144    self.apr_util_path = 'apr-util'
145    self.apr_iconv_path = 'apr-iconv'
146    self.serf_path = None
147    self.bdb_path = None
148    self.httpd_path = None
149    self.libintl_path = None
150    self.zlib_path = 'zlib'
151    self.openssl_path = None
152    self.jdk_path = None
153    self.junit_path = None
154    self.swig_path = None
155    self.py3c_path = None
156    self.vs_version = '2002'
157    self.sln_version = '7.00'
158    self.vcproj_version = '7.00'
159    self.vcproj_extension = '.vcproj'
160    self.sqlite_path = 'sqlite-amalgamation'
161    self.skip_sections = { 'mod_dav_svn': None,
162                           'mod_authz_svn': None,
163                           'mod_dontdothat' : None,
164                           'libsvn_auth_kwallet': None,
165                           'libsvn_auth_gnome_keyring': None }
166
167    # Instrumentation options
168    self.disable_shared = None
169    self.static_apr = None
170    self.static_openssl = None
171    self.instrument_apr_pools = None
172    self.instrument_purify_quantify = None
173    self.sasl_path = None
174    self.cpp_defines = []
175    self.user_macros = []
176
177    # NLS options
178    self.enable_nls = None
179
180    for opt, val in options:
181      if opt == '--with-berkeley-db':
182        self.bdb_path = val
183      elif opt == '--with-apr':
184        self.apr_path = val
185      elif opt == '--with-apr-util':
186        self.apr_util_path = val
187      elif opt == '--with-apr-iconv':
188        self.apr_iconv_path = val
189      elif opt == '--with-serf':
190        self.serf_path = val
191      elif opt == '--with-httpd':
192        self.httpd_path = val
193        del self.skip_sections['mod_dav_svn']
194        del self.skip_sections['mod_authz_svn']
195        del self.skip_sections['mod_dontdothat']
196      elif opt == '--with-libintl':
197        self.libintl_path = val
198        self.enable_nls = 1
199      elif opt == '--with-jdk':
200        self.jdk_path = val
201      elif opt == '--with-junit':
202        self.junit_path = val
203      elif opt == '--with-zlib':
204        self.zlib_path = val
205      elif opt == '--with-swig':
206        self.swig_path = val
207      elif opt == '--with-py3c':
208        self.py3c_path = val
209      elif opt == '--with-sqlite':
210        self.sqlite_path = val
211      elif opt == '--with-sasl':
212        self.sasl_path = val
213      elif opt == '--with-openssl':
214        self.openssl_path = val
215      elif opt == '--enable-purify':
216        self.instrument_purify_quantify = 1
217        self.instrument_apr_pools = 1
218      elif opt == '--enable-quantify':
219        self.instrument_purify_quantify = 1
220      elif opt == '--enable-pool-debug':
221        self.instrument_apr_pools = 1
222      elif opt == '--enable-nls':
223        self.enable_nls = 1
224      elif opt == '--disable-shared':
225        self.disable_shared = 1
226      elif opt == '--with-static-apr':
227        self.static_apr = 1
228      elif opt == '--with-static-openssl':
229        self.static_openssl = 1
230      elif opt == '-D':
231        self.cpp_defines.append(val)
232      elif opt == '--vsnet-version':
233        if val == '2002' or re.match('^7(\.\d+)?$', val):
234          self.vs_version = '2002'
235          self.sln_version = '7.00'
236          self.vcproj_version = '7.00'
237          self.vcproj_extension = '.vcproj'
238        elif val == '2003' or re.match('^8(\.\d+)?$', val):
239          self.vs_version = '2003'
240          self.sln_version = '8.00'
241          self.vcproj_version = '7.10'
242          self.vcproj_extension = '.vcproj'
243        elif val == '2005' or re.match('^9(\.\d+)?$', val):
244          self.vs_version = '2005'
245          self.sln_version = '9.00'
246          self.vcproj_version = '8.00'
247          self.vcproj_extension = '.vcproj'
248        elif val == '2008' or re.match('^10(\.\d+)?$', val):
249          self.vs_version = '2008'
250          self.sln_version = '10.00'
251          self.vcproj_version = '9.00'
252          self.vcproj_extension = '.vcproj'
253        elif val == '2010':
254          self.vs_version = '2010'
255          self.sln_version = '11.00'
256          self.vcproj_version = '10.0'
257          self.vcproj_extension = '.vcxproj'
258        elif val == '2012' or val == '11':
259          self.vs_version = '2012'
260          self.sln_version = '12.00'
261          self.vcproj_version = '11.0'
262          self.vcproj_extension = '.vcxproj'
263        elif val == '2013' or val == '12':
264          self.vs_version = '2013'
265          self.sln_version = '12.00'
266          self.vcproj_version = '12.0'
267          self.vcproj_extension = '.vcxproj'
268        elif val == '2015' or val == '14':
269          self.vs_version = '2015'
270          self.sln_version = '12.00'
271          self.vcproj_version = '14.0'
272          self.vcproj_extension = '.vcxproj'
273        elif val == '2017' or val == '15':
274          self.vs_version = '2017'
275          self.sln_version = '12.00'
276          self.vcproj_version = '14.1'
277          self.vcproj_extension = '.vcxproj'
278        elif val == '2019' or val == '16':
279          self.vs_version = '2019'
280          self.sln_version = '12.00'
281          self.vcproj_version = '14.2'
282          self.vcproj_extension = '.vcxproj'
283        elif re.match('^20\d+$', val):
284          print('WARNING: Unknown VS.NET version "%s",'
285                ' assuming VS2012. Your VS can probably upgrade')
286          self.vs_version = '2012'
287          self.sln_version = '12.00'
288          self.vcproj_version = '11.0'
289          self.vcproj_extension = '.vcxproj'
290        elif re.match('^1\d+$', val):
291          self.vs_version = val
292          self.sln_version = '12.00'
293          self.vcproj_version = val + '.0'
294          self.vcproj_extension = '.vcxproj'
295        else:
296          print('WARNING: Unknown VS.NET version "%s",'
297                 ' assuming "%s"\n' % (val, '7.00'))
298
299
300  def __init__(self, fname, verfname, options, find_libs=True):
301
302    # parse (and save) the options that were passed to us
303    self.parse_options(options)
304
305    # Initialize parent
306    gen_base.GeneratorBase.__init__(self, fname, verfname, options)
307
308    # These files will be excluded from the build when they're not
309    # explicitly listed as project sources.
310    self._excluded_from_build = frozenset(self.private_includes
311                                          + self.private_built_includes)
312
313    if find_libs:
314      self.find_libraries(False)
315
316  def find_libraries(self, show_warnings):
317    "find required and optional libraries"
318
319    # Required dependencies
320    self._find_apr()
321    self._find_apr_util_etc()
322    self._find_zlib()
323    self._find_sqlite(show_warnings)
324    self._find_lz4()
325    self._find_utf8proc()
326
327    # Optional dependencies
328    self._find_httpd(show_warnings)
329    self._find_bdb(show_warnings)
330    self._find_openssl(show_warnings)
331    self._find_serf(show_warnings)
332    self._find_sasl(show_warnings)
333    self._find_libintl(show_warnings)
334
335    self._find_jdk(show_warnings)
336
337    # Swig (optional) dependencies
338    if self._find_swig(show_warnings):
339      self._find_perl(show_warnings)
340      # py3c is required to build python bindings, show check it first
341      if self._find_py3c(show_warnings):
342        self._find_python(show_warnings)
343      self._find_ruby(show_warnings)
344
345  def _find_apr(self):
346    "Find the APR library and version"
347
348    minimal_apr_version = (1, 4, 0)
349
350    if not self.apr_path:
351      sys.stderr.write("ERROR: Use '--with-apr' option to configure APR " + \
352                       "location.\n")
353      sys.exit(1)
354
355    inc_base = os.path.join(self.apr_path, 'include')
356
357    if os.path.isfile(os.path.join(inc_base, 'apr-1', 'apr_version.h')):
358      inc_path = os.path.join(inc_base, 'apr-1')
359    elif os.path.isfile(os.path.join(inc_base, 'apr_version.h')):
360      inc_path = inc_base
361    else:
362      sys.stderr.write("ERROR: 'apr_version' not found.\n")
363      sys.stderr.write("Use '--with-apr' option to configure APR location.\n")
364      sys.exit(1)
365
366    version_file_path = os.path.join(inc_path, 'apr_version.h')
367    txt = open(version_file_path).read()
368
369    vermatch = re.search(r'^\s*#define\s+APR_MAJOR_VERSION\s+(\d+)', txt, re.M)
370    major = int(vermatch.group(1))
371
372    vermatch = re.search(r'^\s*#define\s+APR_MINOR_VERSION\s+(\d+)', txt, re.M)
373    minor = int(vermatch.group(1))
374
375    vermatch = re.search(r'^\s*#define\s+APR_PATCH_VERSION\s+(\d+)', txt, re.M)
376    patch = int(vermatch.group(1))
377
378    version = (major, minor, patch)
379    self.apr_version = apr_version = '%d.%d.%d' % version
380
381    if version < minimal_apr_version:
382      sys.stderr.write("ERROR: apr %s or higher is required "
383                       "(%s found)\n" % (
384                          '.'.join(str(v) for v in minimal_apr_version),
385                          self.apr_version))
386      sys.exit(1)
387
388    suffix = ''
389    if major > 0:
390        suffix = '-%d' % major
391
392    defines = []
393    if self.static_apr:
394      lib_name = 'apr%s.lib' % suffix
395      lib_dir = os.path.join(self.apr_path, 'LibR')
396      dll_dir = None
397      debug_dll_dir = None
398      dll_name = None
399      defines.extend(["APR_DECLARE_STATIC"])
400
401      if not os.path.isdir(lib_dir) and \
402         os.path.isfile(os.path.join(self.apr_path, 'lib', lib_name)):
403        # Installed APR instead of APR-Source
404        lib_dir = os.path.join(self.apr_path, 'lib')
405        debug_lib_dir = None
406      else:
407        debug_lib_dir = os.path.join(self.apr_path, 'LibD')
408    else:
409      lib_name = 'libapr%s.lib' % suffix
410
411      if os.path.isfile(os.path.join(self.apr_path, 'lib', lib_name)):
412        # Installed APR instead of APR-Source
413        lib_dir = os.path.join(self.apr_path, 'lib')
414        debug_lib_dir = None
415      else:
416        lib_dir = os.path.join(self.apr_path, 'Release')
417        if os.path.isfile(os.path.join(self.apr_path, 'Debug', lib_name)):
418          debug_lib_dir = os.path.join(self.apr_path, 'Debug')
419        else:
420          debug_lib_dir = None
421
422      dll_name = 'libapr%s.dll' % suffix
423      if os.path.isfile(os.path.join(lib_dir, dll_name)):
424        dll_dir = lib_dir
425        debug_dll_dir = debug_lib_dir
426      else:
427        dll_dir = os.path.join(self.apr_path, 'bin')
428        debug_dll_dir = None
429
430    extra_bin = []
431
432    if dll_dir:
433      bin_files = os.listdir(dll_dir)
434      if debug_dll_dir and os.path.isdir(debug_dll_dir):
435        debug_bin_files = os.listdir(debug_dll_dir)
436      else:
437        debug_bin_files = bin_files
438
439      for bin in bin_files:
440        if bin in debug_bin_files:
441          if re.match('^(lib)?apr[-_].*' + suffix + '(d)?.dll$', bin):
442            extra_bin.append(bin)
443
444    self._libraries['apr'] = SVNCommonLibrary('apr', inc_path, lib_dir, lib_name,
445                                              apr_version,
446                                              debug_lib_dir=debug_lib_dir,
447                                              dll_dir=dll_dir,
448                                              dll_name=dll_name,
449                                              debug_dll_dir=debug_dll_dir,
450                                              defines=defines,
451                                              extra_bin=extra_bin)
452
453  def _find_apr_util_etc(self):
454    "Find the APR-util library and version"
455
456    minimal_aprutil_version = (1, 3, 0)
457
458    inc_base = os.path.join(self.apr_util_path, 'include')
459
460    if os.path.isfile(os.path.join(inc_base, 'apr-1', 'apu_version.h')):
461      inc_path = os.path.join(inc_base, 'apr-1')
462    elif os.path.isfile(os.path.join(inc_base, 'apu_version.h')):
463      inc_path = inc_base
464    else:
465      sys.stderr.write("ERROR: 'apu_version' not found.\n")
466      sys.stderr.write("Use '--with-apr-util' option to configure APR-Util location.\n")
467      sys.exit(1)
468
469    version_file_path = os.path.join(inc_path, 'apu_version.h')
470    txt = open(version_file_path).read()
471
472    vermatch = re.search(r'^\s*#define\s+APU_MAJOR_VERSION\s+(\d+)', txt, re.M)
473    major = int(vermatch.group(1))
474
475    vermatch = re.search(r'^\s*#define\s+APU_MINOR_VERSION\s+(\d+)', txt, re.M)
476    minor = int(vermatch.group(1))
477
478    vermatch = re.search(r'^\s*#define\s+APU_PATCH_VERSION\s+(\d+)', txt, re.M)
479    patch = int(vermatch.group(1))
480
481    version = (major, minor, patch)
482    self.aprutil_version = aprutil_version = '%d.%d.%d' % version
483
484    if version < minimal_aprutil_version:
485      sys.stderr.write("ERROR: apr-util %s or higher is required "
486                       "(%s found)\n" % (
487                          '.'.join(str(v) for v in minimal_aprutil_version),
488                          aprutil_version))
489      sys.exit(1)
490
491    suffix = ''
492    if major > 0:
493        suffix = '-%d' % major
494
495    defines = []
496    if self.static_apr:
497      lib_name = 'aprutil%s.lib' % suffix
498      lib_dir = os.path.join(self.apr_util_path, 'LibR')
499      dll_dir = None
500      debug_dll_dir = None
501      dll_name = None
502      defines.extend(["APU_DECLARE_STATIC"])
503
504      if not os.path.isdir(lib_dir) and \
505         os.path.isfile(os.path.join(self.apr_util_path, 'lib', lib_name)):
506        # Installed APR-Util instead of APR-Util-Source
507        lib_dir = os.path.join(self.apr_util_path, 'lib')
508        debug_lib_dir = None
509      else:
510        debug_lib_dir = os.path.join(self.apr_util_path, 'LibD')
511    else:
512      lib_name = 'libaprutil%s.lib' % suffix
513      lib_dir = os.path.join(self.apr_util_path, 'Release')
514
515      if not os.path.isdir(lib_dir) and \
516         os.path.isfile(os.path.join(self.apr_util_path, 'lib', lib_name)):
517        # Installed APR-Util instead of APR-Util-Source
518        lib_dir = os.path.join(self.apr_util_path, 'lib')
519        debug_lib_dir = lib_dir
520      else:
521        debug_lib_dir = os.path.join(self.apr_util_path, 'Debug')
522
523      dll_name = 'libaprutil%s.dll' % suffix
524      if os.path.isfile(os.path.join(lib_dir, dll_name)):
525        dll_dir = lib_dir
526        debug_dll_dir = debug_lib_dir
527      else:
528        dll_dir = os.path.join(self.apr_util_path, 'bin')
529        debug_dll_dir = None
530
531    extra_bin = []
532
533    if dll_dir:
534      bin_files = os.listdir(dll_dir)
535      if debug_dll_dir and os.path.isdir(debug_dll_dir):
536        debug_bin_files = os.listdir(debug_dll_dir)
537      else:
538        debug_bin_files = bin_files
539
540      for bin in bin_files:
541        if bin in debug_bin_files:
542          if re.match('^(lib)?aprutil[-_].*' + suffix + '(d)?.dll$', bin):
543            extra_bin.append(bin)
544
545    self._libraries['aprutil'] = SVNCommonLibrary('apr-util', inc_path, lib_dir,
546                                                   lib_name,
547                                                   aprutil_version,
548                                                   debug_lib_dir=debug_lib_dir,
549                                                   dll_dir=dll_dir,
550                                                   dll_name=dll_name,
551                                                   debug_dll_dir=debug_dll_dir,
552                                                   defines=defines,
553                                                   extra_bin=extra_bin)
554
555    # Perhaps apr-util can also provide memcached support
556    if version >= (1, 3, 0) :
557      self._libraries['apr_memcache'] = SVNCommonLibrary(
558                                          'apr_memcache', inc_path, lib_dir,
559                                          None, aprutil_version,
560                                          defines=['SVN_HAVE_MEMCACHE'])
561
562    # And now find expat
563    # If we have apr-util as a source location, it is in a subdir.
564    # If we have an install package it is in the lib subdir
565    if os.path.exists(os.path.join(self.apr_util_path, 'xml/expat')):
566      inc_path = os.path.join(self.apr_util_path, 'xml/expat/lib')
567      lib_dir = os.path.join(self.apr_util_path, 'xml/expat/lib/LibR')
568      debug_lib_dir = os.path.join(self.apr_util_path, 'xml/expat/lib/LibD')
569    else:
570      inc_path = os.path.join(self.apr_util_path, 'include')
571      lib_dir = os.path.join(self.apr_util_path, 'lib')
572      debug_lib_dir = None
573
574    version_file_path = os.path.join(inc_path, 'expat.h')
575
576    if not os.path.exists(version_file_path):
577      sys.stderr.write("ERROR: '%s' not found.\n" % version_file_path);
578      sys.stderr.write("Use '--with-apr-util' option to configure APR-Util's XML location.\n");
579      sys.exit(1)
580
581    txt = open(version_file_path).read()
582
583    vermatch = re.search(r'^\s*#define\s+XML_MAJOR_VERSION\s+(\d+)', txt, re.M)
584    major = int(vermatch.group(1))
585
586    vermatch = re.search(r'^\s*#define\s+XML_MINOR_VERSION\s+(\d+)', txt, re.M)
587    minor = int(vermatch.group(1))
588
589    vermatch = re.search(r'^\s*#define\s+XML_MICRO_VERSION\s+(\d+)', txt, re.M)
590    patch = int(vermatch.group(1))
591
592    # apr-Util 0.9-1.4 compiled expat to 'xml.lib', but apr-util 1.5 switched
593    # to the more common 'libexpat.lib'
594    libname = 'libexpat.lib'
595    if not os.path.exists(os.path.join(lib_dir, 'libexpat.lib')):
596      libname = 'xml.lib'
597
598    version = (major, minor, patch)
599    xml_version = '%d.%d.%d' % version
600
601    self._libraries['xml'] = SVNCommonLibrary('expat', inc_path, lib_dir,
602                                               libname, xml_version,
603                                               debug_lib_dir = debug_lib_dir,
604                                               defines=['XML_STATIC'])
605
606  def _find_httpd(self, show_warnings):
607    "Find Apache HTTPD and version"
608
609    minimal_httpd_version = (2, 2, 0)
610    if not self.httpd_path:
611      return
612
613    inc_base = os.path.join(self.httpd_path, 'include')
614
615    if os.path.isfile(os.path.join(inc_base, 'apache26', 'ap_release.h')):
616      inc_path = os.path.join(inc_base, 'apache26')
617    elif os.path.isfile(os.path.join(inc_base, 'apache24', 'ap_release.h')):
618      inc_path = os.path.join(inc_base, 'apache24')
619    elif os.path.isfile(os.path.join(inc_base, 'apache22', 'ap_release.h')):
620      inc_path = os.path.join(inc_base, 'apache22')
621    elif os.path.isfile(os.path.join(inc_base, 'apache20', 'ap_release.h')):
622      inc_path = os.path.join(inc_base, 'apache20')
623    elif os.path.isfile(os.path.join(inc_base, 'apache2', 'ap_release.h')):
624      inc_path = os.path.join(inc_base, 'apache2')
625    elif os.path.isfile(os.path.join(inc_base, 'apache', 'ap_release.h')):
626      inc_path = os.path.join(inc_base, 'apache')
627    elif os.path.isfile(os.path.join(inc_base, 'ap_release.h')):
628      inc_path = inc_base
629    else:
630      if show_warnings:
631        print('WARNING: \'ap_release.h\' not found')
632        print("Use '--with-httpd' to configure openssl location.");
633      return
634
635    version_file_path = os.path.join(inc_path, 'ap_release.h')
636    txt = open(version_file_path).read()
637
638    vermatch = re.search(r'^\s*#define\s+AP_SERVER_MAJORVERSION_NUMBER\s+(\d+)',
639                         txt, re.M)
640    major = int(vermatch.group(1))
641
642    vermatch = re.search(r'^\s*#define\s+AP_SERVER_MINORVERSION_NUMBER\s+(\d+)',
643                         txt, re.M)
644    minor = int(vermatch.group(1))
645
646    vermatch = re.search(r'^\s*#define\s+AP_SERVER_PATCHLEVEL_NUMBER\s+(\d+)',
647                         txt, re.M)
648    patch = int(vermatch.group(1))
649
650    version = (major, minor, patch)
651    httpd_version = '%d.%d.%d' % version
652
653    if version < minimal_httpd_version:
654      if show_warnings:
655        print("WARNING: httpd %s or higher is required "
656                        "(%s found)\n" % (
657                          '.'.join(str(v) for v in minimal_httpd_version),
658                          httpd_version))
659      return
660
661    lib_name = 'libhttpd.lib'
662    lib_base = self.httpd_path
663
664    debug_lib_dir = None
665
666    if os.path.isfile(os.path.join(lib_base, 'lib', lib_name)):
667      # Install location
668      lib_dir = os.path.join(lib_base, 'lib')
669    elif os.path.isfile(os.path.join(lib_base, 'Release', lib_name)):
670      # Source location
671      lib_dir = os.path.join(lib_base, 'Release')
672      if os.path.isfile(os.path.join(lib_base, 'Debug', lib_name)):
673        debug_lib_dir = os.path.join(lib_base, 'Debug')
674
675    # Our modules run inside httpd, so we don't have to find binaries
676
677    self._libraries['httpd'] = SVNCommonLibrary('httpd', inc_path, lib_dir, lib_name,
678                                                httpd_version,
679                                                debug_lib_dir=debug_lib_dir,
680                                                defines=['AP_DECLARE_EXPORT'])
681
682    # And now find mod_dav
683
684    if os.path.isfile(os.path.join(inc_path, 'mod_dav.h')):
685      # Install location, we are lucky
686      inc_path = inc_path
687    elif os.path.isfile(os.path.join(lib_base, 'modules/dav/main/mod_dav.h')):
688      # Source location
689      inc_path = os.path.join(lib_base, 'modules/dav/main')
690    else:
691      if show_warnings:
692        print("WARNING: Can't find mod_dav.h in the httpd directory")
693        return
694
695    lib_name = 'mod_dav.lib'
696    if os.path.isfile(os.path.join(lib_dir, lib_name)):
697      # Same location as httpd
698      lib_dir = lib_dir
699    elif os.path.isfile(os.path.join(lib_base, 'modules/dav/main/Release', lib_name)):
700      # Source location
701      lib_dir = os.path.join(lib_base, 'modules/dav/main/Release')
702
703      if os.path.isfile(os.path.join(lib_base, 'modules/dav/main/Debug', lib_name)):
704        debug_lib_dir = os.path.join(lib_base, 'modules/dav/main/Debug')
705      else:
706        debug_lib_dir = None
707    else:
708      if show_warnings:
709        print("WARNING: Can't find mod_dav.lib in the httpd directory")
710        return
711
712    self._libraries['mod_dav'] = SVNCommonLibrary('mod_dav', inc_path, lib_dir, lib_name,
713                                                  httpd_version,
714                                                  debug_lib_dir=debug_lib_dir)
715
716  def _find_zlib(self):
717    "Find the ZLib library and version"
718
719    minimal_zlib_version = (1, 2, 5)
720
721    if not self.zlib_path or not os.path.isdir(self.zlib_path):
722      sys.stderr.write("ERROR: '%s' not found.\n" % self.zlib_path);
723      sys.stderr.write("Use '--with-zlib' option to configure ZLib location.\n");
724      sys.exit(1)
725
726    if os.path.isdir(os.path.join(self.zlib_path, 'include')):
727      # We have an install location
728      inc_path = os.path.join(self.zlib_path, 'include')
729      lib_path = os.path.join(self.zlib_path, 'lib')
730
731      # Different build options produce different library names :(
732      if os.path.exists(os.path.join(lib_path, 'zlibstatic.lib')):
733        # CMake default: zlibstatic.lib (static) and zlib.lib (dll)
734        lib_name = 'zlibstatic.lib'
735      elif os.path.exists(os.path.join(lib_path, 'zlibstat.lib')):
736        # Visual Studio project file default: zlibstat.lib (static) and zlibwapi.lib (dll)
737        lib_name = 'zlibstat.lib'
738      else:
739        # Standard makefile produces zlib.lib (static) and zdll.lib (dll)
740        lib_name = 'zlib.lib'
741      debug_lib_name = None
742    else:
743      # We have a source location
744      inc_path = lib_path = self.zlib_path
745      lib_name = 'zlibstat.lib'
746      debug_lib_name = 'zlibstatD.lib'
747
748    version_file_path = os.path.join(inc_path, 'zlib.h')
749
750    if not os.path.exists(version_file_path):
751      sys.stderr.write("ERROR: '%s' not found.\n" % version_file_path);
752      sys.stderr.write("Use '--with-zlib' option to configure ZLib location.\n");
753      sys.exit(1)
754
755    txt = open(version_file_path).read()
756    vermatch = re.search(
757                r'^\s*#define\s+ZLIB_VERSION\s+"(\d+)\.(\d+)\.(\d+)(?:\.\d)?"',
758                 txt, re.M)
759
760    version = tuple(map(int, vermatch.groups()))
761    self.zlib_version = '%d.%d.%d' % version
762
763    if version < minimal_zlib_version:
764      sys.stderr.write("ERROR: ZLib %s or higher is required "
765                       "(%s found)\n" % (
766                          '.'.join(str(v) for v in minimal_zlib_version),
767                          self.zlib_version))
768      sys.exit(1)
769
770    self._libraries['zlib'] = SVNCommonLibrary('zlib', inc_path, lib_path, lib_name,
771                                                self.zlib_version,
772                                                debug_lib_name=debug_lib_name)
773
774  def _find_bdb(self, show_warnings):
775    "Find the Berkeley DB library and version"
776
777    # try default path to detect BDB support, unless different path is
778    # specified so to keep pre 1.10-behavior for BDB detection on Windows
779    bdb_path = 'db4-win32'
780
781    if self.bdb_path:
782      bdb_path = self.bdb_path
783
784    inc_path = os.path.join(bdb_path, 'include')
785    db_h_path = os.path.join(inc_path, 'db.h')
786
787    if not os.path.isfile(db_h_path):
788      if show_warnings and self.bdb_path:
789        print('WARNING: \'%s\' not found' % (db_h_path,))
790        print("Use '--with-berkeley-db' to configure BDB location.");
791      return
792
793    # Obtain bdb version from db.h
794    txt = open(db_h_path).read()
795
796    maj_match = re.search(r'DB_VERSION_MAJOR\s+(\d+)', txt)
797    min_match = re.search(r'DB_VERSION_MINOR\s+(\d+)', txt)
798    patch_match = re.search(r'DB_VERSION_PATCH\s+(\d+)', txt)
799
800    if maj_match and min_match and patch_match:
801      ver = (int(maj_match.group(1)),
802             int(min_match.group(1)),
803             int(patch_match.group(1)))
804    else:
805      return
806
807    version = '%d.%d.%d' % ver
808    versuffix = '%d%d' % (ver[0], ver[1])
809
810    # Before adding "60" to this list, see build/ac-macros/berkeley-db.m4.
811    if versuffix not in (
812            '50', '51', '52', '53',
813            '40', '41', '42', '43', '44', '45', '46', '47', '48',
814       ):
815      return
816
817    lib_dir = os.path.join(bdb_path, 'lib')
818    lib_name = 'libdb%s.lib' % (versuffix,)
819
820    if not os.path.exists(os.path.join(lib_dir, lib_name)):
821      return
822
823    # Do we have a debug version?
824    debug_lib_name = 'libdb%sd.lib' % (versuffix,)
825    if not os.path.isfile(os.path.join(lib_dir, debug_lib_name)):
826      debug_lib_name = None
827
828    dll_dir = os.path.join(bdb_path, 'bin')
829
830    # Are there binaries we should copy for testing?
831    dll_name = os.path.splitext(lib_name)[0] + '.dll'
832    if not os.path.isfile(os.path.join(dll_dir, dll_name)):
833      dll_name = None
834
835    if debug_lib_name:
836      debug_dll_name = os.path.splitext(debug_lib_name)[0] + '.dll'
837      if not os.path.isfile(os.path.join(dll_dir, debug_dll_name)):
838        debug_dll_name = None
839    else:
840      debug_dll_name = None
841
842    # Usually apr-util doesn't find BDB on Windows, so we help apr-util
843    # by defining the value ourselves (Legacy behavior)
844    defines = ['APU_HAVE_DB=1', 'SVN_LIBSVN_FS_LINKS_FS_BASE']
845
846    self._libraries['db'] = SVNCommonLibrary('db', inc_path, lib_dir, lib_name,
847                                              version,
848                                              debug_lib_name=debug_lib_name,
849                                              dll_dir=dll_dir,
850                                              dll_name=dll_name,
851                                              debug_dll_name=debug_dll_name,
852                                              defines=defines)
853
854  def _find_openssl(self, show_warnings):
855    "Find openssl"
856
857    if not self.openssl_path:
858      return
859
860    version_path = os.path.join(self.openssl_path, 'inc32/openssl/opensslv.h')
861    if os.path.isfile(version_path):
862      # We have an OpenSSL Source location
863      # For legacy reason
864      inc_dir = os.path.join(self.openssl_path, 'inc32')
865      if self.static_openssl:
866        lib_dir = os.path.join(self.openssl_path, 'out32')
867        bin_dir = None
868      else:
869        lib_dir = os.path.join(self.openssl_path, 'out32dll')
870        bin_dir = lib_dir
871    elif os.path.isfile(os.path.join(self.openssl_path,
872                        'include/openssl/opensslv.h')):
873      version_path = os.path.join(self.openssl_path,
874                                  'include/openssl/opensslv.h')
875      inc_dir = os.path.join(self.openssl_path, 'include')
876      lib_dir = os.path.join(self.openssl_path, 'lib')
877      if self.static_openssl:
878        bin_dir = None
879      else:
880        bin_dir = os.path.join(self.openssl_path, 'bin')
881    else:
882      if show_warnings:
883        print('WARNING: \'opensslv.h\' not found')
884        print("Use '--with-openssl' to configure openssl location.");
885      return
886
887    txt = open(version_path).read()
888
889    vermatch = re.search(
890      r'#\s*define\s+OPENSSL_VERSION_TEXT\s+"OpenSSL\s+((\d+)\.(\d+).(\d+)([^ -]*))',
891      txt)
892
893    version = (int(vermatch.group(2)),
894               int(vermatch.group(3)),
895               int(vermatch.group(4)))
896    openssl_version = vermatch.group(1)
897
898    libcrypto = 'libcrypto'
899    libssl = 'libssl'
900    versuffix = '-%d_%d' % version[0:2]
901    if version < (1, 1, 0):
902      libcrypto = 'libeay32'
903      libssl = 'ssleay32'
904      versuffix = ''
905
906    self._libraries['openssl'] = SVNCommonLibrary('openssl', inc_dir, lib_dir,
907                                                  '%s.lib' % (libssl,),
908                                                  openssl_version,
909                                                  dll_name='%s%s.dll' %
910                                                      (libssl, versuffix),
911                                                  dll_dir=bin_dir)
912
913    self._libraries['libcrypto'] = SVNCommonLibrary('openssl', inc_dir, lib_dir,
914                                                    '%s.lib' % (libcrypto,),
915                                                    openssl_version,
916                                                    dll_name='%s%s.dll' %
917                                                      (libcrypto, versuffix),
918                                                    dll_dir=bin_dir)
919
920  def _find_perl(self, show_warnings):
921    "Find the right perl library name to link swig bindings with"
922
923    fp = os.popen('perl -MConfig -e ' + escape_shell_arg(
924                  'print "$Config{libperl}\\n"; '
925                  'print "$Config{PERL_REVISION}.$Config{PERL_VERSION}.'
926                          '$Config{PERL_SUBVERSION}\\n"; '
927                  'print "$Config{archlib}\\n"'), 'r')
928    try:
929      line = fp.readline()
930      if line:
931        perl_lib = line.strip()
932      else:
933        return
934
935      line = fp.readline()
936      if line:
937        perl_version = line.strip()
938        perl_ver = perl_version.split('.')
939      else:
940        return
941
942      line = fp.readline()
943      if line:
944        lib_dir = os.path.join(line.strip(), 'CORE')
945        inc_dir = lib_dir
946    finally:
947      fp.close()
948
949    perl_ver = tuple(map(int, perl_ver))
950    forced_includes = []
951
952    if perl_ver >= (5, 18, 0):
953      forced_includes.append('swigutil_pl__pre_perl.h')
954
955    self._libraries['perl'] = SVNCommonLibrary('perl', inc_dir, lib_dir,
956                                               perl_lib, perl_version,
957                                               forced_includes=forced_includes)
958
959  def _find_ruby(self, show_warnings):
960    "Find the right Ruby library name to link swig bindings with"
961
962    lib_dir = None
963    inc_dirs = []
964
965    # Pass -W0 to stifle the "-e:1: Use RbConfig instead of obsolete
966    # and deprecated Config." warning if we are using Ruby 1.9.
967    fp = os.popen('ruby -rrbconfig -W0 -e ' + escape_shell_arg(
968                  "puts RbConfig::CONFIG['ruby_version'];"
969                  "puts RbConfig::CONFIG['LIBRUBY'];"
970                  "puts RbConfig::CONFIG['libdir'];"
971                  "puts RbConfig::CONFIG['rubyhdrdir'];"
972                  "puts RbConfig::CONFIG['arch'];"), 'r')
973    try:
974      line = fp.readline()
975      if line:
976        ruby_version = line.strip()
977
978      line = fp.readline()
979      if line:
980        ruby_lib = line.strip()
981
982      line = fp.readline()
983      if line:
984        lib_dir = line.strip()
985
986      line = fp.readline()
987      if line:
988        inc_base = line.strip()
989        inc_dirs = [inc_base]
990
991      line = fp.readline()
992      if line:
993        inc_dirs.append(os.path.join(inc_base, line.strip()))
994
995    finally:
996      fp.close()
997
998    if not lib_dir:
999      return
1000
1001    # Visual C++ prior to VS2015 doesn't have a standard compliant snprintf
1002    if self.vs_version < '2015':
1003      defines = ['snprintf=_snprintf']
1004    else:
1005      defines = []
1006
1007    ver = ruby_version.split('.')
1008    ver = tuple(map(int, ver))
1009    if ver >= (1, 8, 0):
1010      defines.extend(["HAVE_RB_ERRINFO"])
1011
1012    forced_includes = []
1013
1014    if ver >= (1, 8, 0):
1015      # Swig redefines NUM2LL as NUM2LONG if it isn't defined, but on Windows
1016      # ruby 1.8+ declares NUM2LL as a static inline function.
1017      # (LL2NUM and NUM2ULL don't have these problems)
1018      defines.extend(['NUM2LL=NUM2LL'])
1019
1020    if ver >= (1, 9, 0):
1021      forced_includes.append('swigutil_rb__pre_ruby.h')
1022      defines.extend(["SVN_SWIG_RUBY__CUSTOM_RUBY_CONFIG"])
1023
1024    self._libraries['ruby'] = SVNCommonLibrary('ruby', inc_dirs, lib_dir,
1025                                               ruby_lib, ruby_version,
1026                                               defines=defines,
1027                                               forced_includes=forced_includes)
1028
1029  def _find_python(self, show_warnings):
1030    "Find the appropriate options for creating SWIG-based Python modules"
1031
1032    try:
1033      from distutils import sysconfig
1034
1035      inc_dir = sysconfig.get_python_inc()
1036      lib_dir = os.path.join(sysconfig.PREFIX, "libs")
1037    except ImportError:
1038      return
1039
1040    if sys.version_info[0] >= 3:
1041      if self.swig_version < (3, 0, 10):
1042        if show_warnings:
1043          print("WARNING: Subversion Python bindings for Python 3 require SWIG 3.0.10 or newer")
1044        return
1045      if self.swig_version < (4, 0, 0):
1046        opts = "-python -py3 -nofastunpack -modern"
1047      else:
1048        opts = "-python -py3 -nofastunpack"
1049    else:
1050      if not ((1, 3, 24) <= self.swig_version < (4, 0, 0)):
1051        if show_warnings:
1052          print("WARNING: Subversion Python bindings for Python 2 require 1.3.24 <= SWIG < 4.0.0")
1053        return
1054      opts = "-python -classic"
1055
1056    self.user_macros.append(UserMacro("SWIG_PY_OPTS", opts))
1057    self._libraries['python'] = SVNCommonLibrary('python', inc_dir, lib_dir, None,
1058                                                 sys.version.split(' ')[0])
1059
1060  def _find_py3c(self, show_warnings):
1061    "Find the py3c library which is used in SWIG python bindings"
1062    show_warnings = True
1063    # Assume a default path, unless otherwise specified
1064    py3c_path = "py3c"
1065
1066    if self.py3c_path:
1067      py3c_path = self.py3c_path
1068
1069    py3c_path = os.path.abspath(py3c_path)
1070    inc_path = os.path.join(py3c_path, 'include')
1071    py3c_hdr_path = os.path.join(inc_path, 'py3c.h')
1072
1073    pc_path = os.path.join(py3c_path, 'py3c.pc.in')
1074
1075    if not os.path.isfile(py3c_hdr_path):
1076      if show_warnings:
1077        print('WARNING: "%s" not found' % py3c_hdr_path)
1078        print('Use "--with-py3c" to configure py3c location.')
1079      return False
1080
1081    with open(pc_path) as fp:
1082      txt = fp.read()
1083
1084    ver_match = re.search(r'Version:\s+([0-9.]+)', txt)
1085
1086    if not ver_match:
1087      if show_warnings:
1088        print("WARNING: Failed to find version in '%s'" % pc_path)
1089      return False
1090
1091    py3c_version = ver_match.group(1)
1092
1093    self._libraries['py3c'] = SVNCommonLibrary('py3c', inc_path, None,
1094                                               None, py3c_version)
1095
1096    return True
1097
1098  def _find_jdk(self, show_warnings):
1099    "Find details about an installed jdk"
1100
1101    jdk_path = self.jdk_path
1102    self.jdk_path = None # No jdk on errors
1103
1104    minimal_jdk_version = (1, 0, 0) # ### Provide sane default
1105
1106    if not jdk_path:
1107      jdk_ver = None
1108      try:
1109        try:
1110          # Python >=3.0
1111          import winreg
1112        except ImportError:
1113          # Python <3.0
1114          import _winreg as winreg
1115        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
1116                           r"SOFTWARE\JavaSoft\Java Development Kit")
1117        # Find the newest JDK version.
1118        num_values = winreg.QueryInfoKey(key)[1]
1119        for i in range(num_values):
1120          (name, value, key_type) = winreg.EnumValue(key, i)
1121          if name == "CurrentVersion":
1122            jdk_ver = value
1123            break
1124
1125        # Find the JDK path.
1126        if jdk_ver is not None:
1127          key = winreg.OpenKey(key, jdk_ver)
1128          num_values = winreg.QueryInfoKey(key)[1]
1129          for i in range(num_values):
1130            (name, value, key_type) = winreg.EnumValue(key, i)
1131            if name == "JavaHome":
1132              jdk_path = value
1133              break
1134        winreg.CloseKey(key)
1135      except (ImportError, EnvironmentError):
1136        pass
1137
1138    if not jdk_path or not os.path.isdir(jdk_path):
1139      return
1140
1141    try:
1142      # Apparently a 1.8 javac writes its version output to stderr, while
1143      # a 1.10 javac writes it to stdout. To catch them all, we redirect
1144      # stderr to stdout.
1145      outfp = subprocess.Popen([os.path.join(jdk_path, 'bin', 'javac.exe'),
1146                               '-version'], stdout=subprocess.PIPE,
1147                               stderr=subprocess.STDOUT).stdout
1148      line = outfp.read().decode('utf8')
1149      if line:
1150        vermatch = re.search(r'(([0-9]+(\.[0-9]+)+)(_[._0-9]+)?)', line, re.M)
1151      else:
1152        vermatch = None
1153
1154      if vermatch:
1155        version = tuple(map(int, vermatch.groups()[1].split('.')))
1156        versionstr = vermatch.groups()[0]
1157      else:
1158        if show_warnings:
1159          print('Could not find installed JDK,')
1160        return
1161      outfp.close()
1162    except OSError:
1163      if show_warnings:
1164        print('Could not find installed JDK,')
1165      return
1166
1167    if version < minimal_jdk_version:
1168      if show_warnings:
1169        print('Found java jdk %s, but >= %s is required. '
1170              'javahl will not be built.\n' % \
1171              (versionstr, '.'.join(str(v) for v in minimal_jdk_version)))
1172      return
1173
1174    self.jdk_path = jdk_path
1175    inc_dirs = [
1176        os.path.join(jdk_path, 'include'),
1177        os.path.join(jdk_path, 'include', 'win32'),
1178      ]
1179
1180    lib_dir = os.path.join(jdk_path, 'lib')
1181
1182    # The JDK provides .lib files, but we currently don't use these.
1183    self._libraries['java_sdk'] = SVNCommonLibrary('java-sdk', inc_dirs,
1184                                                   lib_dir, None,
1185                                                   versionstr)
1186
1187  def _find_swig(self, show_warnings):
1188    "Find details about an installed swig"
1189
1190    minimal_swig_version = (1, 3, 25)
1191
1192    if not self.swig_path:
1193      swig_exe = 'swig.exe'
1194    else:
1195      swig_exe = os.path.abspath(os.path.join(self.swig_path, 'swig.exe'))
1196
1197    if self.swig_path is not None:
1198      self.swig_exe = os.path.abspath(os.path.join(self.swig_path, 'swig.exe'))
1199    else:
1200      self.swig_exe = 'swig'
1201
1202    swig_version = None
1203    try:
1204      fp = subprocess.Popen([self.swig_exe, '-version'],
1205                            stdout=subprocess.PIPE).stdout
1206      txt = fp.read().decode('utf8')
1207      if txt:
1208        vermatch = re.search(r'^SWIG\ Version\ (\d+)\.(\d+)\.(\d+)', txt, re.M)
1209      else:
1210        vermatch = None
1211
1212      if vermatch:
1213        swig_version = tuple(map(int, vermatch.groups()))
1214      fp.close()
1215    except OSError:
1216      swig_version = None
1217
1218    if not swig_version:
1219      if show_warnings:
1220        print('Could not find installed SWIG')
1221      return False
1222
1223    swig_ver = '%d.%d.%d' % (swig_version)
1224    if swig_version < minimal_swig_version:
1225      if show_warnings:
1226        print('Found swig %s, but >= %s is required. '
1227              'the swig bindings will not be built.\n' %
1228              (swig_version, '.'.join(str(v) for v in minimal_swig_version)))
1229      return
1230
1231    try:
1232      fp = subprocess.Popen([self.swig_exe, '-swiglib'],
1233                            stdout=subprocess.PIPE).stdout
1234      lib_dir = fp.readline().decode('utf8').strip()
1235      fp.close()
1236    except OSError:
1237      lib_dir = None
1238      fp.close()
1239
1240    if not lib_dir:
1241      if show_warnings:
1242        print('Could not find libdir of installed SWIG')
1243      return False
1244
1245    if (not self.swig_path and
1246        os.path.isfile(os.path.join(lib_dir, '../swig.exe'))):
1247      self.swig_path = os.path.dirname(lib_dir)
1248
1249    inc_dirs = [
1250        'subversion/bindings/swig',
1251        'subversion/bindings/swig/proxy',
1252        'subversion/bindings/swig/include',
1253      ]
1254
1255    self.swig_libdir = lib_dir
1256    self.swig_version = swig_version
1257
1258    self._libraries['swig'] = SVNCommonLibrary('swig', inc_dirs, lib_dir, None,
1259                                               swig_ver)
1260    return True
1261
1262  def _get_serf_version(self, inc_dir):
1263    "Retrieves the serf version from serf.h"
1264
1265    # shouldn't be called unless serf is there
1266    assert inc_dir and os.path.exists(inc_dir)
1267
1268    serf_ver_maj = None
1269    serf_ver_min = None
1270    serf_ver_patch = None
1271
1272    # serf.h should be present
1273    if not os.path.exists(os.path.join(inc_dir, 'serf.h')):
1274      return None, None, None
1275
1276    txt = open(os.path.join(inc_dir, 'serf.h')).read()
1277
1278    maj_match = re.search(r'SERF_MAJOR_VERSION\s+(\d+)', txt)
1279    min_match = re.search(r'SERF_MINOR_VERSION\s+(\d+)', txt)
1280    patch_match = re.search(r'SERF_PATCH_VERSION\s+(\d+)', txt)
1281    if maj_match:
1282      serf_ver_maj = int(maj_match.group(1))
1283    if min_match:
1284      serf_ver_min = int(min_match.group(1))
1285    if patch_match:
1286      serf_ver_patch = int(patch_match.group(1))
1287
1288    return serf_ver_maj, serf_ver_min, serf_ver_patch
1289
1290  def _find_serf(self, show_warnings):
1291    "Check if serf and its dependencies are available"
1292
1293    minimal_serf_version = (1, 3, 4)
1294
1295    if not self.serf_path:
1296      return
1297
1298    inc_dir = self.serf_path
1299
1300    if os.path.isfile(os.path.join(inc_dir, 'serf.h')):
1301      # Source layout
1302      lib_dir = self.serf_path
1303      debug_lib_dir = None
1304      inc_dir = self.serf_path
1305    elif os.path.isfile(os.path.join(self.serf_path, 'include/serf-1/serf.h')):
1306      # Install layout
1307      inc_dir = os.path.join(self.serf_path, 'include/serf-1')
1308      lib_dir = os.path.join(self.serf_path, 'lib')
1309      debug_lib_dir = None
1310    elif os.path.isfile(os.path.join(self.serf_path, 'include/serf-2/serf.h')):
1311      # Install layout
1312      inc_dir = os.path.join(self.serf_path, 'include/serf-2')
1313      lib_dir = os.path.join(self.serf_path, 'lib')
1314      debug_lib_dir = None
1315    else:
1316      if show_warnings:
1317        print('WARNING: \'serf.h\' not found')
1318        print("Use '--with-serf' to configure serf location.");
1319      return
1320
1321    version = self._get_serf_version(inc_dir)
1322    serf_version = '.'.join(str(v) for v in version)
1323
1324    if version < minimal_serf_version:
1325      if show_warnings:
1326        print('Found serf %s, but >= %s is required. '
1327              'ra_serf will not be built.\n' %
1328              (serf_version, '.'.join(str(v) for v in minimal_serf_version)))
1329      return
1330
1331    serf_ver_maj = version[0]
1332
1333    if serf_ver_maj > 0:
1334      lib_name = 'serf-%d.lib' % (serf_ver_maj,)
1335    else:
1336      lib_name = 'serf.lib'
1337
1338    defines = ['SVN_HAVE_SERF', 'SVN_LIBSVN_RA_LINKS_RA_SERF']
1339
1340    self._libraries['serf'] = SVNCommonLibrary('serf', inc_dir, lib_dir,
1341                                                lib_name, serf_version,
1342                                                debug_lib_dir=debug_lib_dir,
1343                                                defines=defines)
1344
1345  def _find_sasl(self, show_warnings):
1346    "Check if sals is available"
1347
1348    minimal_sasl_version = (2, 0, 0)
1349
1350    if not self.sasl_path:
1351      return
1352
1353    inc_dir = os.path.join(self.sasl_path, 'include')
1354
1355    version_file_path = os.path.join(inc_dir, 'sasl.h')
1356
1357    if not os.path.isfile(version_file_path):
1358      if show_warnings:
1359        print('WARNING: \'%s\' not found' % (version_file_path,))
1360        print("Use '--with-sasl' to configure sasl location.");
1361      return
1362
1363    txt = open(version_file_path).read()
1364
1365    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MAJOR\s+(\d+)', txt, re.M)
1366    major = int(vermatch.group(1))
1367
1368    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MINOR\s+(\d+)', txt, re.M)
1369    minor = int(vermatch.group(1))
1370
1371    vermatch = re.search(r'^\s*#define\s+SASL_VERSION_STEP\s+(\d+)', txt, re.M)
1372    patch = int(vermatch.group(1))
1373
1374    version = (major, minor, patch)
1375    sasl_version = '.'.join(str(v) for v in version)
1376
1377    if version < minimal_sasl_version:
1378      if show_warnings:
1379        print('Found sasl %s, but >= %s is required. '
1380              'sals support will not be built.\n' %
1381              (sasl_version, '.'.join(str(v) for v in minimal_serf_version)))
1382      return
1383
1384    lib_dir = os.path.join(self.sasl_path, 'lib')
1385
1386    if os.path.isfile(os.path.join(lib_dir, 'libsasl.dll')):
1387      dll_dir = lib_dir
1388      dll_name = 'libsasl.dll'
1389    elif os.path.isfile(os.path.join(self.sasl_path, 'bin', 'libsasl.dll')):
1390      dll_dir = os.path.join(self.sasl_path, 'bin')
1391      dll_name = 'libsasl.dll'
1392    else:
1393      # Probably a static compilation
1394      dll_dir = None
1395      dll_name = None
1396
1397    self._libraries['sasl'] = SVNCommonLibrary('sasl', inc_dir, lib_dir,
1398                                               'libsasl.lib', sasl_version,
1399                                               dll_dir=dll_dir,
1400                                               dll_name=dll_name,
1401                                               defines=['SVN_HAVE_SASL'])
1402
1403  def _find_libintl(self, show_warnings):
1404    "Find gettext support"
1405    minimal_libintl_version = (0, 14, 1)
1406
1407    if not self.enable_nls or not self.libintl_path:
1408      return;
1409
1410    # We support 2 scenarios.
1411    if os.path.isfile(os.path.join(self.libintl_path, 'inc', 'libintl.h')) and\
1412       os.path.isfile(os.path.join(self.libintl_path, 'lib', 'intl3_svn.lib')):
1413
1414      # 1. Subversion's custom libintl based on gettext 0.14.1
1415      inc_dir = os.path.join(self.libintl_path, 'inc')
1416      lib_dir = os.path.join(self.libintl_path, 'lib')
1417      dll_dir = os.path.join(self.libintl_path, 'bin')
1418
1419      lib_name = 'intl3_svn.lib'
1420      dll_name = 'intl3_svn.dll'
1421    elif os.path.isfile(os.path.join(self.libintl_path, \
1422                                     'include', 'libintl.h')):
1423      # 2. A gettext install
1424      inc_dir = os.path.join(self.libintl_path, 'include')
1425      lib_dir = os.path.join(self.libintl_path, 'lib')
1426      dll_dir = os.path.join(self.libintl_path, 'bin')
1427
1428      lib_name = 'intl.lib'
1429      dll_name = 'intl.dll'
1430    else:
1431      if (show_warnings):
1432        print('WARNING: \'libintl.h\' not found')
1433        print("Use '--with-libintl' to configure libintl location.")
1434      return
1435
1436    version_file_path = os.path.join(inc_dir, 'libintl.h')
1437    txt = open(version_file_path).read()
1438
1439    match = re.search(r'^\s*#define\s+LIBINTL_VERSION\s+((0x)?[0-9A-Fa-f]+)',
1440                      txt, re.M)
1441
1442    ver = int(match.group(1), 0)
1443    version = (ver >> 16, (ver >> 8) & 0xFF, ver & 0xFF)
1444
1445    libintl_version = '.'.join(str(v) for v in version)
1446
1447    if version < minimal_libintl_version:
1448      if show_warnings:
1449        print('Found libintl %s, but >= %s is required.\n' % \
1450              (libintl_version,
1451               '.'.join(str(v) for v in minimal_libintl_version)))
1452      return
1453
1454    self._libraries['intl'] = SVNCommonLibrary('libintl', inc_dir, lib_dir,
1455                                               lib_name, libintl_version,
1456                                               dll_dir=dll_dir,
1457                                               dll_name=dll_name)
1458
1459  def _find_sqlite(self, show_warnings):
1460    "Find the Sqlite library and version"
1461
1462    minimal_sqlite_version = (3, 8, 2)
1463
1464    # For SQLite we support 3 scenarios:
1465    # - Installed in standard directory layout
1466    # - Installed in legacy directory layout
1467    # - Amalgamation compiled directly into our libraries
1468
1469    sqlite_base = self.sqlite_path
1470
1471    lib_dir = None
1472    dll_dir = None
1473    dll_name = None
1474    defines = []
1475
1476    lib_name = 'sqlite3.lib'
1477
1478    if os.path.isfile(os.path.join(sqlite_base, 'include/sqlite3.h')):
1479      # Standard layout
1480      inc_dir = os.path.join(sqlite_base, 'include')
1481      lib_dir = os.path.join(sqlite_base, 'lib')
1482
1483      # We assume a static library, but let's support shared in this case
1484      if os.path.isfile(os.path.join(sqlite_base, 'bin/sqlite3.dll')):
1485        dll_dir = os.path.join(sqlite_base, 'bin')
1486        dll_name = 'sqlite3.dll'
1487    elif os.path.isfile(os.path.join(sqlite_base, 'inc/sqlite3.h')):
1488      # Standard layout
1489      inc_dir = os.path.join(sqlite_base, 'inc')
1490      lib_dir = os.path.join(sqlite_base, 'lib')
1491
1492      # We assume a static library, but let's support shared in this case
1493      if os.path.isfile(os.path.join(sqlite_base, 'bin/sqlite3.dll')):
1494        dll_dir = os.path.join(sqlite_base, 'bin')
1495        dll_name = 'sqlite3.dll'
1496    elif (os.path.isfile(os.path.join(sqlite_base, 'sqlite3.h'))
1497          and os.path.isfile(os.path.join(sqlite_base, 'sqlite3.c'))):
1498      # Amalgamation
1499      inc_dir = sqlite_base
1500      lib_dir = None
1501      lib_name = None
1502      defines.append('SVN_SQLITE_INLINE')
1503    else:
1504      sys.stderr.write("ERROR: SQLite not found\n")
1505      sys.stderr.write("Use '--with-sqlite' option to configure sqlite location.\n");
1506      sys.exit(1)
1507
1508    version_file_path = os.path.join(inc_dir, 'sqlite3.h')
1509
1510    txt = open(version_file_path).read()
1511
1512    match = re.search(r'^\s*#define\s+SQLITE_VERSION\s+'
1513                      r'"(\d+)\.(\d+)\.(\d+)(?:\.(\d))?"', txt, re.M)
1514
1515    version = match.groups()
1516
1517    # Sqlite doesn't add patch numbers for their ordinary releases
1518    if not version[3]:
1519      version = version[0:3]
1520
1521    version = tuple(map(int, version))
1522
1523    sqlite_version = '.'.join(str(v) for v in version)
1524
1525    if version < minimal_sqlite_version:
1526      sys.stderr.write("ERROR: sqlite %s or higher is required "
1527                       "(%s found)\n" % (
1528                          '.'.join(str(v) for v in minimal_sqlite_version),
1529                          sqlite_version))
1530      sys.exit(1)
1531
1532    self._libraries['sqlite'] = SVNCommonLibrary('sqlite', inc_dir, lib_dir,
1533                                                 lib_name, sqlite_version,
1534                                                 dll_dir=dll_dir,
1535                                                 dll_name=dll_name,
1536                                                 defines=defines)
1537
1538  def _find_lz4(self):
1539    "Find the LZ4 library"
1540
1541    # For now, we always use the internal (bundled) library.
1542    version_file_path = os.path.join('subversion', 'libsvn_subr',
1543                                     'lz4', 'lz4internal.h')
1544    txt = open(version_file_path).read()
1545
1546    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MAJOR\s+(\d+)',
1547                         txt, re.M)
1548    major = int(vermatch.group(1))
1549
1550    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MINOR\s+(\d+)',
1551                         txt, re.M)
1552    minor = int(vermatch.group(1))
1553
1554    vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_RELEASE\s+(\d+)',
1555                         txt, re.M)
1556    rel = vermatch.group(1)
1557
1558    lz4_version = '%d.%d.%s' % (major, minor, rel)
1559    self._libraries['lz4'] = SVNCommonLibrary('lz4', None, None, None,
1560                                              lz4_version, internal=True,
1561                                              defines=['SVN_INTERNAL_LZ4'])
1562
1563  def _find_utf8proc(self):
1564    "Find the Utf8proc library"
1565
1566    # For now, we always use the internal (bundled) library.
1567    version_file_path = os.path.join('subversion', 'libsvn_subr',
1568                                     'utf8proc', 'utf8proc_internal.h')
1569    txt = open(version_file_path).read()
1570
1571    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MAJOR\s+(\d+)',
1572                         txt, re.M)
1573    major = int(vermatch.group(1))
1574
1575    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MINOR\s+(\d+)',
1576                         txt, re.M)
1577    minor = int(vermatch.group(1))
1578
1579    vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_PATCH\s+(\d+)',
1580                         txt, re.M)
1581    patch = int(vermatch.group(1))
1582
1583    utf8proc_version = '%d.%d.%d' % (major, minor, patch)
1584    self._libraries['utf8proc'] = SVNCommonLibrary('utf8proc', None, None,
1585                                                   None, utf8proc_version,
1586                                                   internal=True,
1587                                        defines=['SVN_INTERNAL_UTF8PROC'])
1588
1589# ============================================================================
1590# This is a cut-down and modified version of code from:
1591#   subversion/subversion/bindings/swig/python/svn/core.py
1592#
1593if sys.platform == "win32":
1594  _escape_shell_arg_re = re.compile(r'(\\+)(\"|$)')
1595
1596  def escape_shell_arg(arg):
1597    # The (very strange) parsing rules used by the C runtime library are
1598    # described at:
1599    # http://msdn.microsoft.com/library/en-us/vclang/html/_pluslang_Parsing_C.2b2b_.Command.2d.Line_Arguments.asp
1600
1601    # double up slashes, but only if they are followed by a quote character
1602    arg = re.sub(_escape_shell_arg_re, r'\1\1\2', arg)
1603
1604    # surround by quotes and escape quotes inside
1605    arg = '"' + arg.replace('"', '"^""') + '"'
1606    return arg
1607
1608else:
1609  def escape_shell_arg(str):
1610    return "'" + str.replace("'", "'\\''") + "'"
1611