1#!/usr/local/bin/python3.8
2
3"""
4Configuration program for botan (http://botan.randombit.net/)
5  (C) 2009-2011 Jack Lloyd
6  Distributed under the terms of the Botan license
7
8Tested with CPython 2.6, 2.7, 3.1 and PyPy 1.5
9
10Python 2.5 works if you change the exception catching syntax:
11   perl -pi -e 's/except (.*) as (.*):/except $1, $2:/g' configure.py
12
13Jython - Target detection does not work (use --os and --cpu)
14
15CPython 2.4 and earlier are not supported
16
17Has not been tested with IronPython
18"""
19
20import sys
21import os
22import os.path
23import platform
24import re
25import shlex
26import shutil
27import string
28import subprocess
29import logging
30import getpass
31import time
32import errno
33import optparse
34
35# Avoid useless botan_version.pyc (Python 2.6 or higher)
36if 'dont_write_bytecode' in sys.__dict__:
37    sys.dont_write_bytecode = True
38
39import botan_version
40
41def flatten(l):
42    return sum(l, [])
43
44def get_vc_revision():
45    try:
46        mtn = subprocess.Popen(['mtn', 'automate', 'heads'],
47                               stdout=subprocess.PIPE,
48                               stderr=subprocess.PIPE,
49                               universal_newlines=True)
50
51        (stdout, stderr) = mtn.communicate()
52
53        if mtn.returncode != 0:
54            logging.debug('Error getting rev from monotone - %d (%s)'
55                          % (mtn.returncode, stderr))
56            return 'unknown'
57
58        rev = str(stdout).strip()
59        logging.debug('Monotone reported revision %s' % (rev))
60
61        return 'mtn:' + rev
62    except Exception as e:
63        logging.debug('Error getting rev from monotone - %s' % (e))
64        return 'unknown'
65
66
67class BuildConfigurationInformation(object):
68
69    """
70    Version information
71    """
72    version_major = botan_version.release_major
73    version_minor = botan_version.release_minor
74    version_patch = botan_version.release_patch
75    version_so_rev = botan_version.release_so_abi_rev
76
77    version_datestamp = botan_version.release_datestamp
78
79    version_vc_rev = botan_version.release_vc_rev
80    version_string = '%d.%d.%d' % (version_major, version_minor, version_patch)
81
82    """
83    Constructor
84    """
85    def __init__(self, options, modules):
86
87        if self.version_vc_rev is None:
88            self.version_vc_rev = get_vc_revision()
89
90        self.build_dir = os.path.join(options.with_build_dir, 'build')
91
92        self.checkobj_dir = os.path.join(self.build_dir, 'checks')
93        self.libobj_dir = os.path.join(self.build_dir, 'lib')
94
95        self.python_dir = os.path.join(options.src_dir, 'wrap', 'python')
96
97        self.boost_python = options.boost_python
98
99        self.doc_output_dir = os.path.join(self.build_dir, 'docs')
100
101        self.pyobject_dir = os.path.join(self.build_dir, 'python')
102
103        self.include_dir = os.path.join(self.build_dir, 'include')
104        self.botan_include_dir = os.path.join(self.include_dir, 'botan')
105        self.internal_include_dir = os.path.join(self.botan_include_dir, 'internal')
106
107        self.sources = sorted(flatten([mod.sources() for mod in modules]))
108        self.internal_headers = sorted(flatten([m.internal_headers() for m in modules]))
109
110        if options.via_amalgamation:
111            self.build_sources = ['botan_all.cpp']
112            self.build_internal_headers = []
113        else:
114            self.build_sources = self.sources
115            self.build_internal_headers = self.internal_headers
116
117        self.public_headers = sorted(flatten([m.public_headers() for m in modules]))
118
119        checks_dir = os.path.join(options.base_dir, 'checks')
120
121        self.check_sources = sorted(
122            [os.path.join(checks_dir, file) for file in os.listdir(checks_dir)
123             if file.endswith('.cpp')])
124
125        self.python_sources = sorted(
126            [os.path.join(self.python_dir, file)
127             for file in os.listdir(self.python_dir)
128             if file.endswith('.cpp')])
129
130        self.manual_dir = os.path.join(self. doc_output_dir, 'manual')
131
132        def build_doc_commands():
133            yield '$(COPY) readme.txt %s' % (self.doc_output_dir)
134
135            if options.with_sphinx:
136                yield 'sphinx-build $(SPHINX_OPTS) -b html doc %s' % (
137                    self.manual_dir)
138            else:
139                yield '$(COPY) doc/*.txt %s' % (self.manual_dir)
140
141            if options.with_doxygen:
142                yield 'doxygen %s/botan.doxy' % (self.build_dir)
143
144        self.build_doc_commands = '\n'.join(['\t' + s for s in build_doc_commands()])
145
146        def build_dirs():
147            yield self.checkobj_dir
148            yield self.libobj_dir
149            yield self.botan_include_dir
150            yield self.internal_include_dir
151            yield os.path.join(self.doc_output_dir, 'manual')
152            if options.with_doxygen:
153                yield os.path.join(self.doc_output_dir, 'doxygen')
154
155            if self.boost_python:
156                yield self.pyobject_dir
157
158        self.build_dirs = list(build_dirs())
159
160    def pkg_config_file(self):
161        return 'botan-%d.%d.pc' % (self.version_major,
162                                   self.version_minor)
163
164    def config_shell_script(self):
165        return 'botan-config-%d.%d' % (self.version_major,
166                                       self.version_minor)
167
168    def username(self):
169        return getpass.getuser()
170
171    def hostname(self):
172        return platform.node()
173
174    def timestamp(self):
175        return time.ctime()
176
177"""
178Handle command line options
179"""
180def process_command_line(args):
181
182    parser = optparse.OptionParser(
183        formatter = optparse.IndentedHelpFormatter(max_help_position = 50),
184        version = BuildConfigurationInformation.version_string)
185
186    parser.add_option('--verbose', action='store_true', default=False,
187                      help='Show debug messages')
188    parser.add_option('--quiet', action='store_true', default=False,
189                      help='Show only warnings and errors')
190
191    target_group = optparse.OptionGroup(parser, 'Target options')
192
193    target_group.add_option('--cpu',
194                            help='set the target processor type/model')
195
196    target_group.add_option('--os',
197                            help='set the target operating system')
198
199    target_group.add_option('--cc', dest='compiler',
200                            help='set the desired build compiler')
201
202    target_group.add_option('--cc-bin', dest='compiler_binary',
203                            metavar='BINARY',
204                            help='set the name of the compiler binary')
205
206    target_group.add_option('--with-endian', metavar='ORDER', default=None,
207                            help='override guess of CPU byte order')
208
209    target_group.add_option('--with-unaligned-mem',
210                            dest='unaligned_mem', action='store_true',
211                            default=None,
212                            help='enable unaligned memory accesses')
213
214    target_group.add_option('--without-unaligned-mem',
215                            dest='unaligned_mem', action='store_false',
216                            help=optparse.SUPPRESS_HELP)
217
218    for isa_extn_name in ['SSE2', 'SSSE3', 'AltiVec', 'AES-NI', 'movbe']:
219        isa_extn = isa_extn_name.lower()
220
221        target_group.add_option('--enable-%s' % (isa_extn),
222                                help='enable use of %s' % (isa_extn_name),
223                                action='append_const',
224                                const=isa_extn,
225                                dest='enable_isa_extns')
226
227        target_group.add_option('--disable-%s' % (isa_extn),
228                                help=optparse.SUPPRESS_HELP,
229                                action='append_const',
230                                const=isa_extn,
231                                dest='disable_isa_extns')
232
233    build_group = optparse.OptionGroup(parser, 'Build options')
234
235    build_group.add_option('--enable-shared', dest='build_shared_lib',
236                           action='store_true', default=True,
237                            help=optparse.SUPPRESS_HELP)
238    build_group.add_option('--disable-shared', dest='build_shared_lib',
239                           action='store_false',
240                           help='disable building a shared library')
241
242    build_group.add_option('--enable-asm', dest='asm_ok',
243                           action='store_true', default=True,
244                           help=optparse.SUPPRESS_HELP)
245    build_group.add_option('--disable-asm', dest='asm_ok',
246                           action='store_false',
247                           help='disallow use of assembler')
248
249    build_group.add_option('--enable-debug', dest='debug_build',
250                           action='store_true', default=False,
251                           help='enable debug build')
252    build_group.add_option('--disable-debug', dest='debug_build',
253                           action='store_false', help=optparse.SUPPRESS_HELP)
254
255    build_group.add_option('--no-optimizations', dest='no_optimizations',
256                           action='store_true', default=False,
257                           help=optparse.SUPPRESS_HELP)
258
259    build_group.add_option('--gen-amalgamation', dest='gen_amalgamation',
260                           default=False, action='store_true',
261                           help='generate amalgamation files')
262
263    build_group.add_option('--via-amalgamation', dest='via_amalgamation',
264                           default=False, action='store_true',
265                           help='build via amalgamation')
266
267    build_group.add_option('--with-tr1-implementation', metavar='WHICH',
268                           dest='with_tr1', default=None,
269                           help='enable TR1 (choices: none, system, boost)')
270
271    build_group.add_option('--with-build-dir',
272                           metavar='DIR', default='',
273                           help='setup the build in DIR')
274
275    build_group.add_option('--makefile-style', metavar='STYLE', default=None,
276                           help='choose a makefile style (unix or nmake)')
277
278    build_group.add_option('--with-local-config',
279                           dest='local_config', metavar='FILE',
280                           help='include the contents of FILE into build.h')
281
282    build_group.add_option('--distribution-info', metavar='STRING',
283                           help='set distribution specific versioning',
284                           default='unspecified')
285
286    build_group.add_option('--with-sphinx', action='store_true',
287                           default=None,
288                           help='Use Sphinx to generate HTML manual')
289
290    build_group.add_option('--without-sphinx', action='store_false',
291                           dest='with_sphinx', help=optparse.SUPPRESS_HELP)
292
293    build_group.add_option('--with-visibility', action='store_true',
294                           default=None, help=optparse.SUPPRESS_HELP)
295
296    build_group.add_option('--without-visibility', action='store_false',
297                           dest='with_visibility', help=optparse.SUPPRESS_HELP)
298
299    build_group.add_option('--with-doxygen', action='store_true',
300                           default=False,
301                           help='Use Doxygen to generate HTML API docs')
302
303    build_group.add_option('--without-doxygen', action='store_false',
304                           dest='with_doxygen', help=optparse.SUPPRESS_HELP)
305
306    build_group.add_option('--dumb-gcc', dest='dumb_gcc',
307                           action='store_true', default=False,
308                           help=optparse.SUPPRESS_HELP)
309
310    build_group.add_option('--maintainer-mode', dest='maintainer_mode',
311                           action='store_true', default=False,
312                           help=optparse.SUPPRESS_HELP)
313
314    build_group.add_option('--dirty-tree', dest='clean_build_tree',
315                           action='store_false', default=True,
316                           help=optparse.SUPPRESS_HELP)
317
318    build_group.add_option('--link-method',
319                           default=None,
320                           help=optparse.SUPPRESS_HELP)
321
322    wrapper_group = optparse.OptionGroup(parser, 'Wrapper options')
323
324    wrapper_group.add_option('--with-boost-python', dest='boost_python',
325                             default=False, action='store_true',
326                             help='enable Boost.Python wrapper')
327
328    wrapper_group.add_option('--without-boost-python',
329                             dest='boost_python',
330                             action='store_false',
331                             help=optparse.SUPPRESS_HELP)
332
333    wrapper_group.add_option('--with-python-version', dest='python_version',
334                             metavar='N.M',
335                             default='.'.join(map(str, sys.version_info[0:2])),
336                             help='specify Python to build against (eg %default)')
337
338    mods_group = optparse.OptionGroup(parser, 'Module selection')
339
340    mods_group.add_option('--enable-modules', dest='enabled_modules',
341                          metavar='MODS', action='append',
342                          help='enable specific modules')
343    mods_group.add_option('--disable-modules', dest='disabled_modules',
344                          metavar='MODS', action='append',
345                          help='disable specific modules')
346    mods_group.add_option('--no-autoload', action='store_true', default=False,
347                          help='disable automatic loading')
348
349    for lib in ['OpenSSL', 'GNU MP', 'Bzip2', 'Zlib']:
350
351        mod = lib.lower().replace(' ', '')
352
353        mods_group.add_option('--with-%s' % (mod),
354                              help='add support for using %s' % (lib),
355                              action='append_const',
356                              const=mod,
357                              dest='enabled_modules')
358
359        mods_group.add_option('--without-%s' % (mod),
360                              help=optparse.SUPPRESS_HELP,
361                              action='append_const',
362                              const=mod,
363                              dest='disabled_modules')
364
365    install_group = optparse.OptionGroup(parser, 'Installation options')
366
367    install_group.add_option('--prefix', metavar='DIR',
368                             help='set the base install directory')
369    install_group.add_option('--docdir', metavar='DIR',
370                             help='set the documentation install directory')
371    install_group.add_option('--libdir', metavar='DIR',
372                             help='set the library install directory')
373    install_group.add_option('--includedir', metavar='DIR',
374                             help='set the include file install directory')
375
376    parser.add_option_group(target_group)
377    parser.add_option_group(build_group)
378    parser.add_option_group(mods_group)
379    parser.add_option_group(wrapper_group)
380    parser.add_option_group(install_group)
381
382    # These exist only for autoconf compatability (requested by zw for mtn)
383    compat_with_autoconf_options = [
384        'bindir',
385        'datadir',
386        'datarootdir',
387        'dvidir',
388        'exec-prefix',
389        'htmldir',
390        'infodir',
391        'libexecdir',
392        'localedir',
393        'localstatedir',
394        'mandir',
395        'oldincludedir',
396        'pdfdir',
397        'psdir',
398        'sbindir',
399        'sharedstatedir',
400        'sysconfdir'
401        ]
402
403    for opt in compat_with_autoconf_options:
404        parser.add_option('--' + opt, help=optparse.SUPPRESS_HELP)
405
406    (options, args) = parser.parse_args(args)
407
408    if args != []:
409        raise Exception('Unhandled option(s): ' + ' '.join(args))
410    if options.with_endian != None and \
411       options.with_endian not in ['little', 'big']:
412        raise Exception('Bad value to --with-endian "%s"' % (
413            options.with_endian))
414
415    def parse_multiple_enable(modules):
416        if modules is None:
417            return []
418        return sorted(set(flatten([s.split(',') for s in modules])))
419
420    options.enabled_modules = parse_multiple_enable(options.enabled_modules)
421    options.disabled_modules = parse_multiple_enable(options.disabled_modules)
422
423    options.enable_isa_extns = parse_multiple_enable(options.enable_isa_extns)
424    options.disable_isa_extns = parse_multiple_enable(options.disable_isa_extns)
425
426    def enabled_or_disabled_isa(isa):
427        if isa in options.enable_isa_extns:
428            return True
429        if isa in options.disable_isa_extns:
430            return True
431        return False
432
433    isa_deps = {
434        'ssse3': 'sse2',
435        'aes-ni': 'sse2'
436        }
437
438    if 'sse2' in options.disable_isa_extns:
439        for isa in [k for (k,v) in isa_deps.items() if v == 'sse2']:
440            # If explicitly enabled, allow it even if a dependency
441            # violation; trust the user to know what they want
442            if not enabled_or_disabled_isa(isa):
443                options.disable_isa_extns.append(isa)
444
445    for isa in options.enable_isa_extns:
446        if isa in isa_deps:
447            for dep in isa_deps.get(isa, '').split(','):
448                if not enabled_or_disabled_isa(dep):
449                    options.enable_isa_extns.append(dep)
450
451    return options
452
453"""
454Generic lexer function for info.txt and src/build-data files
455"""
456def lex_me_harder(infofile, to_obj, allowed_groups, name_val_pairs):
457
458    # Format as a nameable Python variable
459    def py_var(group):
460        return group.replace(':', '_')
461
462    class LexerError(Exception):
463        def __init__(self, msg, line):
464            self.msg = msg
465            self.line = line
466
467        def __str__(self):
468            return '%s at %s:%d' % (self.msg, infofile, self.line)
469
470    (dirname, basename) = os.path.split(infofile)
471
472    to_obj.lives_in = dirname
473    if basename == 'info.txt':
474        (obj_dir,to_obj.basename) = os.path.split(dirname)
475        if os.access(os.path.join(obj_dir, 'info.txt'), os.R_OK):
476            to_obj.parent_module = os.path.basename(obj_dir)
477        else:
478            to_obj.parent_module = None
479    else:
480        to_obj.basename = basename.replace('.txt', '')
481
482    lexer = shlex.shlex(open(infofile), infofile, posix=True)
483    lexer.wordchars += '|:.<>/,-!+' # handle various funky chars in info.txt
484
485    for group in allowed_groups:
486        to_obj.__dict__[py_var(group)] = []
487    for (key,val) in name_val_pairs.items():
488        to_obj.__dict__[key] = val
489
490    def lexed_tokens(): # Convert to an interator
491        token = lexer.get_token()
492        while token != None:
493            yield token
494            token = lexer.get_token()
495
496    for token in lexed_tokens():
497        match = re.match('<(.*)>', token)
498
499        # Check for a grouping
500        if match is not None:
501            group = match.group(1)
502
503            if group not in allowed_groups:
504                raise LexerError('Unknown group "%s"' % (group),
505                                 lexer.lineno)
506
507            end_marker = '</' + group + '>'
508
509            token = lexer.get_token()
510            while token != end_marker:
511                to_obj.__dict__[py_var(group)].append(token)
512                token = lexer.get_token()
513                if token is None:
514                    raise LexerError('Group "%s" not terminated' % (group),
515                                     lexer.lineno)
516
517        elif token in name_val_pairs.keys():
518            next_val = lexer.get_token()
519
520            if type(to_obj.__dict__[token]) is list:
521                to_obj.__dict__[token].append(next_val)
522            else:
523                to_obj.__dict__[token] = next_val
524
525        else: # No match -> error
526            raise LexerError('Bad token "%s"' % (token), lexer.lineno)
527
528"""
529Convert a lex'ed map (from build-data files) from a list to a dict
530"""
531def force_to_dict(l):
532    return dict(zip(l[::3],l[2::3]))
533
534"""
535Represents the information about a particular module
536"""
537class ModuleInfo(object):
538
539    def __init__(self, infofile):
540
541        lex_me_harder(infofile, self,
542                      ['source', 'header:internal', 'header:public',
543                       'requires', 'os', 'arch', 'cc', 'libs',
544                       'comment', 'warning'],
545                      {
546                        'load_on': 'auto',
547                        'define': [],
548                        'uses_tr1': 'false',
549                        'need_isa': None,
550                        'mp_bits': 0 })
551
552        def extract_files_matching(basedir, suffixes):
553            for (dirpath, dirnames, filenames) in os.walk(basedir):
554                if dirpath == basedir:
555                    for filename in filenames:
556                        if filename.startswith('.'):
557                            continue
558
559                        for suffix in suffixes:
560                            if filename.endswith(suffix):
561                                yield filename
562
563        if self.source == []:
564            self.source = list(extract_files_matching(self.lives_in, ['.cpp', '.S']))
565
566        if self.header_internal == [] and self.header_public == []:
567            self.header_public = list(extract_files_matching(self.lives_in, ['.h']))
568
569        # Coerce to more useful types
570        def convert_lib_list(l):
571            result = {}
572            for (targetlist, vallist) in zip(l[::3], l[2::3]):
573                vals = vallist.split(',')
574                for target in targetlist.split(','):
575                    result[target] = result.setdefault(target, []) + vals
576            return result
577
578        self.libs = convert_lib_list(self.libs)
579
580        def add_dir_name(filename):
581            if filename.count(':') == 0:
582                return os.path.join(self.lives_in, filename)
583
584            # modules can request to add files of the form
585            # MODULE_NAME:FILE_NAME to add a file from another module
586            # For these, assume other module is always in a
587            # neighboring directory; this is true for all current uses
588            return os.path.join(os.path.split(self.lives_in)[0],
589                                *filename.split(':'))
590
591        self.source = [add_dir_name(s) for s in self.source]
592        self.header_internal = [add_dir_name(s) for s in self.header_internal]
593        self.header_public = [add_dir_name(s) for s in self.header_public]
594
595        self.mp_bits = int(self.mp_bits)
596
597        self.uses_tr1 = (True if self.uses_tr1 == 'yes' else False)
598
599        if self.comment != []:
600            self.comment = ' '.join(self.comment)
601        else:
602            self.comment = None
603
604        if self.warning != []:
605            self.warning = ' '.join(self.warning)
606        else:
607            self.warning = None
608
609    def sources(self):
610        return self.source
611
612    def public_headers(self):
613        return self.header_public
614
615    def internal_headers(self):
616        return self.header_internal
617
618    def defines(self):
619        return ['HAS_' + d for d in self.define]
620
621    def compatible_cpu(self, archinfo, options):
622
623        arch_name = archinfo.basename
624        cpu_name = options.cpu
625
626        if self.arch != []:
627            if arch_name not in self.arch and cpu_name not in self.arch:
628                return False
629
630        if self.need_isa != None:
631            if self.need_isa in options.disable_isa_extns:
632                return False # explicitly disabled
633
634            if self.need_isa in options.enable_isa_extns:
635                return True # explicitly enabled
636
637            # Default to whatever the CPU is supposed to support
638            return self.need_isa in archinfo.isa_extensions_in(cpu_name)
639
640        return True
641
642    def compatible_os(self, os):
643        return self.os == [] or os in self.os
644
645    def compatible_compiler(self, cc):
646        return self.cc == [] or cc in self.cc
647
648    def tr1_ok(self, with_tr1):
649        if self.uses_tr1:
650            return with_tr1 in ['boost', 'system']
651        else:
652            return True
653
654    def dependencies(self):
655        # utils is an implicit dep (contains types, etc)
656        deps = self.requires + ['utils']
657        if self.parent_module != None:
658            deps.append(self.parent_module)
659        return deps
660
661    """
662    Ensure that all dependencies of this module actually exist, warning
663    about any that do not
664    """
665    def dependencies_exist(self, modules):
666        all_deps = [s.split('|') for s in self.dependencies()]
667
668        for missing in [s for s in flatten(all_deps) if s not in modules]:
669            logging.warn("Module '%s', dep of '%s', does not exist" % (
670                missing, self.basename))
671
672    def __cmp__(self, other):
673        if self.basename < other.basename:
674            return -1
675        if self.basename == other.basename:
676            return 0
677        return 1
678
679class ArchInfo(object):
680    def __init__(self, infofile):
681        lex_me_harder(infofile, self,
682                      ['aliases', 'submodels', 'submodel_aliases', 'isa_extn'],
683                      { 'endian': None,
684                        'family': None,
685                        'unaligned': 'no'
686                        })
687
688        def convert_isa_list(input):
689            isa_info = {}
690            for line in self.isa_extn:
691                (isa,cpus) = line.split(':')
692                for cpu in cpus.split(','):
693                    isa_info.setdefault(cpu, []).append(isa)
694            return isa_info
695
696        self.isa_extn = convert_isa_list(self.isa_extn)
697
698        self.submodel_aliases = force_to_dict(self.submodel_aliases)
699
700        self.unaligned_ok = (1 if self.unaligned == 'ok' else 0)
701
702    """
703    Return ISA extensions specific to this CPU
704    """
705    def isa_extensions_in(self, cpu_type):
706        return sorted(self.isa_extn.get(cpu_type, []) +
707                      self.isa_extn.get('all', []))
708
709    """
710    Return a list of all submodels for this arch, ordered longest
711    to shortest
712    """
713    def all_submodels(self):
714        return sorted([(k,k) for k in self.submodels] +
715                      [k for k in self.submodel_aliases.items()],
716                      key = lambda k: len(k[0]), reverse = True)
717
718    """
719    Return CPU-specific defines for build.h
720    """
721    def defines(self, options):
722        def form_macro(cpu_name):
723            return cpu_name.upper().replace('.', '').replace('-', '_')
724
725        macros = ['TARGET_ARCH_IS_%s' %
726                  (form_macro(self.basename.upper()))]
727
728        if self.basename != options.cpu:
729            macros.append('TARGET_CPU_IS_%s' % (form_macro(options.cpu)))
730
731        enabled_isas = set(self.isa_extensions_in(options.cpu) +
732                           options.enable_isa_extns)
733        disabled_isas = set(options.disable_isa_extns)
734
735        isa_extensions = sorted(enabled_isas - disabled_isas)
736
737        for isa in isa_extensions:
738            macros.append('TARGET_CPU_HAS_%s' % (form_macro(isa)))
739
740        endian = options.with_endian or self.endian
741
742        if endian != None:
743            macros.append('TARGET_CPU_IS_%s_ENDIAN' % (endian.upper()))
744            logging.info('Assuming CPU is %s endian' % (endian))
745
746        unaligned_ok = options.unaligned_mem
747        if unaligned_ok is None:
748            unaligned_ok = self.unaligned_ok
749            if unaligned_ok:
750                logging.info('Assuming unaligned memory access works')
751
752        if self.family is not None:
753            macros.append('TARGET_CPU_IS_%s_FAMILY' % (self.family.upper()))
754
755        macros.append('TARGET_UNALIGNED_MEMORY_ACCESS_OK %d' % (unaligned_ok))
756
757        return macros
758
759class CompilerInfo(object):
760    def __init__(self, infofile):
761        lex_me_harder(infofile, self,
762                      ['so_link_flags', 'mach_opt', 'mach_abi_linking'],
763                      { 'binary_name': None,
764                        'macro_name': None,
765                        'compile_option': '-c ',
766                        'output_to_option': '-o ',
767                        'add_include_dir_option': '-I',
768                        'add_lib_dir_option': '-L',
769                        'add_lib_option': '-l',
770                        'lib_opt_flags': '',
771                        'check_opt_flags': '',
772                        'debug_flags': '',
773                        'no_debug_flags': '',
774                        'shared_flags': '',
775                        'lang_flags': '',
776                        'warning_flags': '',
777                        'maintainer_warning_flags': '',
778                        'visibility_build_flags': '',
779                        'visibility_attribute': '',
780                        'ar_command': None,
781                        'makefile_style': '',
782                        'has_tr1': False,
783                        })
784
785        self.so_link_flags = force_to_dict(self.so_link_flags)
786        self.mach_abi_linking = force_to_dict(self.mach_abi_linking)
787
788        self.mach_opt_flags = {}
789
790        while self.mach_opt != []:
791            proc = self.mach_opt.pop(0)
792            if self.mach_opt.pop(0) != '->':
793                raise Exception('Parsing err in %s mach_opt' % (self.basename))
794
795            flags = self.mach_opt.pop(0)
796            regex = ''
797
798            if len(self.mach_opt) > 0 and \
799               (len(self.mach_opt) == 1 or self.mach_opt[1] != '->'):
800                regex = self.mach_opt.pop(0)
801
802            self.mach_opt_flags[proc] = (flags,regex)
803
804        del self.mach_opt
805
806    """
807    Return the shared library build flags, if any
808    """
809    def gen_shared_flags(self, options):
810        def flag_builder():
811            if options.build_shared_lib:
812                yield self.shared_flags
813                if options.with_visibility:
814                    yield self.visibility_build_flags
815
816        return ' '.join(list(flag_builder()))
817
818    def gen_visibility_attribute(self, options):
819        if options.build_shared_lib and options.with_visibility:
820            return self.visibility_attribute
821        return ''
822
823    """
824    Return the machine specific ABI flags
825    """
826    def mach_abi_link_flags(self, osname, arch, submodel, debug_p):
827
828        def all():
829            if debug_p:
830                return 'all-debug'
831            return 'all'
832
833        abi_link = set()
834        for what in [all(), osname, arch, submodel]:
835            if self.mach_abi_linking.get(what) != None:
836                abi_link.add(self.mach_abi_linking.get(what))
837
838        if len(abi_link) == 0:
839            return ''
840        return ' ' + ' '.join(abi_link)
841
842    """
843    Return the flags for MACH_OPT
844    """
845    def mach_opts(self, arch, submodel):
846
847        def submodel_fixup(tup):
848            return tup[0].replace('SUBMODEL', submodel.replace(tup[1], ''))
849
850        if submodel == arch:
851            return ''
852
853        if submodel in self.mach_opt_flags:
854            return submodel_fixup(self.mach_opt_flags[submodel])
855        if arch in self.mach_opt_flags:
856            return submodel_fixup(self.mach_opt_flags[arch])
857
858        return ''
859
860    """
861    Return the flags for LIB_OPT
862    """
863    def library_opt_flags(self, options):
864        def gen_flags():
865            if options.debug_build:
866                yield self.debug_flags
867
868            if not options.no_optimizations:
869                yield self.lib_opt_flags
870
871                if not options.debug_build:
872                    yield self.no_debug_flags
873
874        return (' '.join(gen_flags())).strip()
875
876    """
877    Return the command needed to link a shared object
878    """
879    def so_link_command_for(self, osname):
880        if osname in self.so_link_flags:
881            return self.so_link_flags[osname]
882        if 'default' in self.so_link_flags:
883            return self.so_link_flags['default']
884        return ''
885
886    """
887    Return defines for build.h
888    """
889    def defines(self, with_tr1):
890
891        def tr1_macro():
892            if with_tr1:
893                if with_tr1 == 'boost':
894                    return ['USE_BOOST_TR1']
895                elif with_tr1 == 'system':
896                    return ['USE_STD_TR1']
897            elif self.has_tr1:
898                return ['USE_STD_TR1']
899            return []
900
901        return ['BUILD_COMPILER_IS_' + self.macro_name] + tr1_macro()
902
903class OsInfo(object):
904    def __init__(self, infofile):
905        lex_me_harder(infofile, self,
906                      ['aliases', 'target_features'],
907                      { 'os_type': None,
908                        'obj_suffix': 'o',
909                        'so_suffix': 'so',
910                        'static_suffix': 'a',
911                        'ar_command': 'ar crs',
912                        'ar_needs_ranlib': False,
913                        'install_root': '/usr/local',
914                        'header_dir': 'include',
915                        'lib_dir': 'lib',
916                        'doc_dir': 'share/doc',
917                        'build_shared': 'yes',
918                        'install_cmd_data': 'install -m 644',
919                        'install_cmd_exec': 'install -m 755'
920                        })
921
922        self.ar_needs_ranlib = bool(self.ar_needs_ranlib)
923
924        self.build_shared = (True if self.build_shared == 'yes' else False)
925
926    def ranlib_command(self):
927        return ('ranlib' if self.ar_needs_ranlib else 'true')
928
929    def defines(self):
930        return ['TARGET_OS_IS_%s' % (self.basename.upper())] + \
931               ['TARGET_OS_HAS_' + feat.upper()
932                for feat in sorted(self.target_features)]
933
934def fixup_proc_name(proc):
935    proc = proc.lower().replace(' ', '')
936    for junk in ['(tm)', '(r)']:
937        proc = proc.replace(junk, '')
938    return proc
939
940def canon_processor(archinfo, proc):
941    proc = fixup_proc_name(proc)
942
943    # First, try to search for an exact match
944    for ainfo in archinfo.values():
945        if ainfo.basename == proc or proc in ainfo.aliases:
946            return (ainfo.basename, ainfo.basename)
947
948        for (match,submodel) in ainfo.all_submodels():
949            if proc == submodel or proc == match:
950                return (ainfo.basename, submodel)
951
952    logging.debug('Could not find an exact match for CPU "%s"' % (proc))
953
954    # Now, try searching via regex match
955    for ainfo in archinfo.values():
956        for (match,submodel) in ainfo.all_submodels():
957            if re.search(match, proc) != None:
958                logging.debug('Possible match "%s" with "%s" (%s)' % (
959                    proc, match, submodel))
960                return (ainfo.basename, submodel)
961
962    logging.debug('Known CPU names: ' + ' '.join(
963        sorted(flatten([[ainfo.basename] + \
964                        ainfo.aliases + \
965                        [x for (x,_) in ainfo.all_submodels()]
966                        for ainfo in archinfo.values()]))))
967
968    raise Exception('Unknown or unidentifiable processor "%s"' % (proc))
969
970def guess_processor(archinfo):
971    base_proc = platform.machine()
972
973    if base_proc == '':
974        raise Exception('Could not determine target CPU; set with --cpu')
975
976    full_proc = fixup_proc_name(platform.processor()) or base_proc
977
978    for ainfo in archinfo.values():
979        if ainfo.basename == base_proc or base_proc in ainfo.aliases:
980            for (match,submodel) in ainfo.all_submodels():
981                if re.search(match, full_proc) != None:
982                    return (ainfo.basename, submodel)
983
984            return canon_processor(archinfo, ainfo.basename)
985
986    # No matches, so just use the base proc type
987    return canon_processor(archinfo, base_proc)
988
989"""
990Read a whole file into memory as a string
991"""
992def slurp_file(filename):
993    if filename is None:
994        return ''
995    return ''.join(open(filename).readlines())
996
997"""
998Perform template substitution
999"""
1000def process_template(template_file, variables):
1001    class PercentSignTemplate(string.Template):
1002        delimiter = '%'
1003
1004    try:
1005        template = PercentSignTemplate(slurp_file(template_file))
1006        return template.substitute(variables)
1007    except KeyError as e:
1008        raise Exception('Unbound var %s in template %s' % (e, template_file))
1009
1010"""
1011Create the template variables needed to process the makefile, build.h, etc
1012"""
1013def create_template_vars(build_config, options, modules, cc, arch, osinfo):
1014    def make_cpp_macros(macros):
1015        return '\n'.join(['#define BOTAN_' + macro for macro in macros])
1016
1017    """
1018    Figure out what external libraries are needed based on selected modules
1019    """
1020    def link_to():
1021        libs = set()
1022        for module in modules:
1023            for (osname,link_to) in module.libs.items():
1024                if osname == 'all' or osname == osinfo.basename:
1025                    libs |= set(link_to)
1026                else:
1027                    match = re.match('^all!(.*)', osname)
1028                    if match is not None:
1029                        exceptions = match.group(1).split(',')
1030                        if osinfo.basename not in exceptions:
1031                            libs |= set(link_to)
1032        return sorted(libs)
1033
1034    def objectfile_list(sources, obj_dir):
1035        for src in sources:
1036            (dir,file) = os.path.split(os.path.normpath(src))
1037
1038            if dir.startswith('src'):
1039                parts = dir.split(os.sep)[1:]
1040                if file == parts[-1] + '.cpp':
1041                    name = '_'.join(dir.split(os.sep)[1:]) + '.cpp'
1042                else:
1043                    name = '_'.join(dir.split(os.sep)[1:]) + '_' + file
1044            else:
1045                name = file
1046
1047            for src_suffix in ['.cpp', '.S']:
1048                name = name.replace(src_suffix, '.' + osinfo.obj_suffix)
1049
1050            yield os.path.join(obj_dir, name)
1051
1052
1053    def choose_mp_bits():
1054        mp_bits = [mod.mp_bits for mod in modules if mod.mp_bits != 0]
1055
1056        if mp_bits == []:
1057            return 32 # default
1058
1059        # Check that settings are consistent across modules
1060        for mp_bit in mp_bits[1:]:
1061            if mp_bit != mp_bits[0]:
1062                raise Exception('Incompatible mp_bits settings found')
1063
1064        return mp_bits[0]
1065
1066    """
1067    Form snippets of makefile for building each source file
1068    """
1069    def build_commands(sources, obj_dir, flags):
1070        for (obj_file,src) in zip(objectfile_list(sources, obj_dir), sources):
1071            yield '%s: %s\n\t$(CXX) %s%s $(%s_FLAGS) %s$? %s$@\n' % (
1072                obj_file, src,
1073                cc.add_include_dir_option,
1074                build_config.include_dir,
1075                flags,
1076                cc.compile_option,
1077                cc.output_to_option)
1078
1079    def makefile_list(items):
1080        items = list(items) # force evaluation so we can slice it
1081        return (' '*16).join([item + ' \\\n' for item in items[:-1]] +
1082                             [items[-1]])
1083
1084    def prefix_with_build_dir(path):
1085        if options.with_build_dir != None:
1086            return os.path.join(options.with_build_dir, path)
1087        return path
1088
1089    def warning_flags(normal_flags,
1090                      maintainer_flags,
1091                      maintainer_mode):
1092        if maintainer_mode and maintainer_flags != '':
1093            return maintainer_flags
1094        return normal_flags
1095
1096    return {
1097        'version_major':  build_config.version_major,
1098        'version_minor':  build_config.version_minor,
1099        'version_patch':  build_config.version_patch,
1100        'version_vc_rev': build_config.version_vc_rev,
1101        'so_abi_rev':     build_config.version_so_rev,
1102        'version':        build_config.version_string,
1103
1104        'distribution_info': options.distribution_info,
1105
1106        'version_datestamp': build_config.version_datestamp,
1107
1108        'timestamp': build_config.timestamp(),
1109        'user':      build_config.username(),
1110        'hostname':  build_config.hostname(),
1111        'command_line': ' '.join(sys.argv),
1112        'local_config': slurp_file(options.local_config),
1113        'makefile_style': options.makefile_style or cc.makefile_style,
1114
1115        'makefile_path': prefix_with_build_dir('Makefile'),
1116
1117        'prefix': options.prefix or osinfo.install_root,
1118        'libdir': options.libdir or osinfo.lib_dir,
1119        'includedir': options.includedir or osinfo.header_dir,
1120        'docdir': options.docdir or osinfo.doc_dir,
1121
1122        'build_dir': build_config.build_dir,
1123        'doc_output_dir': build_config.doc_output_dir,
1124
1125        'build_doc_commands': build_config.build_doc_commands,
1126
1127        'python_dir': build_config.python_dir,
1128
1129        'os': options.os,
1130        'arch': options.arch,
1131        'submodel': options.cpu,
1132
1133        'mp_bits': choose_mp_bits(),
1134
1135        'cc': (options.compiler_binary or cc.binary_name) +
1136              cc.mach_abi_link_flags(options.os, options.arch,
1137                                     options.cpu, options.debug_build),
1138
1139        'lib_opt': cc.library_opt_flags(options),
1140        'mach_opt': cc.mach_opts(options.arch, options.cpu),
1141        'check_opt': '' if options.no_optimizations else cc.check_opt_flags,
1142        'lang_flags': cc.lang_flags + options.extra_flags,
1143        'warn_flags': warning_flags(cc.warning_flags,
1144                                    cc.maintainer_warning_flags,
1145                                    options.maintainer_mode),
1146
1147        'shared_flags': cc.gen_shared_flags(options),
1148        'visibility_attribute': cc.gen_visibility_attribute(options),
1149
1150        'so_link': cc.so_link_command_for(osinfo.basename),
1151
1152        'link_to': ' '.join([cc.add_lib_option + lib for lib in link_to()]),
1153
1154        'module_defines': make_cpp_macros(sorted(flatten([m.defines() for m in modules]))),
1155
1156        'target_os_defines': make_cpp_macros(osinfo.defines()),
1157
1158        'target_compiler_defines': make_cpp_macros(
1159            cc.defines(options.with_tr1)),
1160
1161        'target_cpu_defines': make_cpp_macros(arch.defines(options)),
1162
1163        'include_files': makefile_list(build_config.public_headers),
1164
1165        'lib_objs': makefile_list(
1166            objectfile_list(build_config.build_sources,
1167                            build_config.libobj_dir)),
1168
1169        'check_objs': makefile_list(
1170            objectfile_list(build_config.check_sources,
1171                            build_config.checkobj_dir)),
1172
1173        'lib_build_cmds': '\n'.join(
1174            build_commands(build_config.build_sources,
1175                           build_config.libobj_dir, 'LIB')),
1176
1177        'check_build_cmds': '\n'.join(
1178            build_commands(build_config.check_sources,
1179                           build_config.checkobj_dir, 'CHECK')),
1180
1181        'python_obj_dir': build_config.pyobject_dir,
1182
1183        'python_objs': makefile_list(
1184            objectfile_list(build_config.python_sources,
1185                            build_config.pyobject_dir)),
1186
1187        'python_build_cmds': '\n'.join(
1188            build_commands(build_config.python_sources,
1189                           build_config.pyobject_dir, 'PYTHON')),
1190
1191        'ar_command': cc.ar_command or osinfo.ar_command,
1192        'ranlib_command': osinfo.ranlib_command(),
1193        'install_cmd_exec': osinfo.install_cmd_exec,
1194        'install_cmd_data': osinfo.install_cmd_data,
1195
1196        'check_prefix': prefix_with_build_dir(''),
1197        'lib_prefix': prefix_with_build_dir(''),
1198
1199        'static_suffix': osinfo.static_suffix,
1200        'so_suffix': osinfo.so_suffix,
1201
1202        'botan_config': prefix_with_build_dir(
1203            os.path.join(build_config.build_dir,
1204                         build_config.config_shell_script())),
1205
1206        'botan_pkgconfig': prefix_with_build_dir(
1207            os.path.join(build_config.build_dir,
1208                         build_config.pkg_config_file())),
1209
1210        'mod_list': '\n'.join(sorted([m.basename for m in modules])),
1211
1212        'python_version': options.python_version
1213        }
1214
1215"""
1216Determine which modules to load based on options, target, etc
1217"""
1218def choose_modules_to_use(modules, archinfo, options):
1219
1220    for mod in modules.values():
1221        mod.dependencies_exist(modules)
1222
1223    to_load = []
1224    maybe_dep = []
1225    not_using_because = {}
1226
1227    def cannot_use_because(mod, reason):
1228        not_using_because.setdefault(reason, []).append(mod)
1229
1230    for modname in options.enabled_modules:
1231        if modname not in modules:
1232            logging.warning("Unknown enabled module %s" % (modname))
1233
1234    for modname in options.disabled_modules:
1235        if modname not in modules:
1236            logging.warning("Unknown disabled module %s" % (modname))
1237
1238    for (modname, module) in modules.items():
1239        if modname in options.disabled_modules:
1240            cannot_use_because(modname, 'disabled by user')
1241        elif modname in options.enabled_modules:
1242            to_load.append(modname) # trust the user
1243
1244        elif not module.compatible_os(options.os):
1245            cannot_use_because(modname, 'incompatible OS')
1246        elif not module.compatible_compiler(options.compiler):
1247            cannot_use_because(modname, 'incompatible compiler')
1248        elif not module.compatible_cpu(archinfo, options):
1249            cannot_use_because(modname, 'incompatible CPU')
1250        elif not module.tr1_ok(options.with_tr1):
1251            cannot_use_because(modname, 'missing TR1')
1252
1253        else:
1254            if module.load_on == 'never':
1255                cannot_use_because(modname, 'disabled as buggy')
1256            elif module.load_on == 'request':
1257                cannot_use_because(modname, 'by request only')
1258            elif module.load_on == 'dep':
1259                maybe_dep.append(modname)
1260
1261            elif module.load_on == 'always':
1262                to_load.append(modname)
1263
1264            elif module.load_on == 'asm_ok':
1265                if options.asm_ok:
1266                    if options.no_autoload:
1267                        maybe_dep.append(modname)
1268                    else:
1269                        to_load.append(modname)
1270                else:
1271                    cannot_use_because(modname,
1272                                       'uses assembly and --disable-asm set')
1273            elif module.load_on == 'auto':
1274                if options.no_autoload:
1275                    maybe_dep.append(modname)
1276                else:
1277                    to_load.append(modname)
1278            else:
1279                logging.warning('Unknown load_on %s in %s' % (
1280                    module.load_on, modname))
1281
1282    dependency_failure = True
1283
1284    while dependency_failure:
1285        dependency_failure = False
1286        for modname in to_load:
1287            for deplist in [s.split('|') for s in modules[modname].dependencies()]:
1288
1289                dep_met = False
1290                for mod in deplist:
1291                    if dep_met is True:
1292                        break
1293
1294                    if mod in to_load:
1295                        dep_met = True
1296                    elif mod in maybe_dep:
1297                        maybe_dep.remove(mod)
1298                        to_load.append(mod)
1299                        dep_met = True
1300
1301                if dep_met == False:
1302                    dependency_failure = True
1303                    if modname in to_load:
1304                        to_load.remove(modname)
1305                    if modname in maybe_dep:
1306                        maybe_dep.remove(modname)
1307                    cannot_use_because(modname, 'dependency failure')
1308
1309    for not_a_dep in maybe_dep:
1310        cannot_use_because(not_a_dep, 'loaded only if needed by dependency')
1311
1312    for reason in sorted(not_using_because.keys()):
1313        disabled_mods = sorted(set([mod for mod in not_using_because[reason]]))
1314
1315        if disabled_mods != []:
1316            logging.info('Skipping, %s - %s' % (
1317                reason, ' '.join(disabled_mods)))
1318
1319    for mod in sorted(to_load):
1320        if mod.startswith('mp_'):
1321            logging.info('Using MP module ' + mod)
1322        if mod.startswith('simd_') and mod != 'simd_engine':
1323            logging.info('Using SIMD module ' + mod)
1324        if modules[mod].comment:
1325            logging.info('%s: %s' % (mod, modules[mod].comment))
1326        if modules[mod].warning:
1327            logging.warning('%s: %s' % (mod, modules[mod].warning))
1328
1329    logging.debug('Loading modules %s', ' '.join(sorted(to_load)))
1330
1331    return [modules[mod] for mod in to_load]
1332
1333"""
1334Load the info files about modules, targets, etc
1335"""
1336def load_info_files(options):
1337
1338    def find_files_named(desired_name, in_path):
1339        for (dirpath, dirnames, filenames) in os.walk(in_path):
1340            if desired_name in filenames:
1341                yield os.path.join(dirpath, desired_name)
1342
1343    modules = dict([(mod.basename, mod) for mod in
1344                    [ModuleInfo(info) for info in
1345                     find_files_named('info.txt', options.src_dir)]])
1346
1347    def list_files_in_build_data(subdir):
1348        for (dirpath, dirnames, filenames) in \
1349                os.walk(os.path.join(options.build_data, subdir)):
1350            for filename in filenames:
1351                if filename.endswith('.txt'):
1352                    yield os.path.join(dirpath, filename)
1353
1354    def form_name(filepath):
1355        return os.path.basename(filepath).replace('.txt', '')
1356
1357    archinfo = dict([(form_name(info), ArchInfo(info))
1358                     for info in list_files_in_build_data('arch')])
1359
1360    osinfo   = dict([(form_name(info), OsInfo(info))
1361                      for info in list_files_in_build_data('os')])
1362
1363    ccinfo = dict([(form_name(info), CompilerInfo(info))
1364                    for info in list_files_in_build_data('cc')])
1365
1366    def info_file_load_report(type, num):
1367        if num > 0:
1368            logging.debug('Loaded %d %s info files' % (num, type))
1369        else:
1370            logging.warning('Failed to load any %s info files' % (type))
1371
1372    info_file_load_report('CPU', len(archinfo));
1373    info_file_load_report('OS', len(osinfo))
1374    info_file_load_report('compiler', len(ccinfo))
1375
1376    return (modules, archinfo, ccinfo, osinfo)
1377
1378"""
1379Perform the filesystem operations needed to setup the build
1380"""
1381def setup_build(build_config, options, template_vars):
1382
1383    """
1384    Choose the link method based on system availablity and user request
1385    """
1386    def choose_link_method(req_method):
1387
1388        def useable_methods():
1389            if 'symlink' in os.__dict__:
1390                yield 'symlink'
1391            if 'link' in os.__dict__:
1392                yield 'hardlink'
1393            yield 'copy'
1394
1395        for method in useable_methods():
1396            if req_method is None or req_method == method:
1397                return method
1398
1399        logging.info('Could not use requested link method %s' % (req_method))
1400        return 'copy'
1401
1402    """
1403    Copy or link the file, depending on what the platform offers
1404    """
1405    def portable_symlink(filename, target_dir, method):
1406
1407        if not os.access(filename, os.R_OK):
1408            logging.warning('Missing file %s' % (filename))
1409            return
1410
1411        if method == 'symlink':
1412            def count_dirs(dir, accum = 0):
1413                if dir in ['', '/', os.path.curdir]:
1414                    return accum
1415                (dir,basename) = os.path.split(dir)
1416                return accum + 1 + count_dirs(dir)
1417
1418            dirs_up = count_dirs(target_dir)
1419
1420            source = os.path.join(os.path.join(*[os.path.pardir]*dirs_up),
1421                                  filename)
1422
1423            target = os.path.join(target_dir, os.path.basename(filename))
1424
1425            os.symlink(source, target)
1426
1427        elif method == 'hardlink':
1428            os.link(filename,
1429                    os.path.join(target_dir, os.path.basename(filename)))
1430
1431        elif method == 'copy':
1432            shutil.copy(filename, target_dir)
1433
1434        else:
1435            raise Exception('Unknown link method %s' % (method))
1436
1437    def choose_makefile_template(style):
1438        if style == 'nmake':
1439            return 'nmake.in'
1440        elif style == 'unix':
1441            return ('unix_shr.in' if options.build_shared_lib else 'unix.in')
1442        else:
1443            raise Exception('Unknown makefile style "%s"' % (style))
1444
1445    # First delete the build tree, if existing
1446    try:
1447        if options.clean_build_tree:
1448            shutil.rmtree(build_config.build_dir)
1449    except OSError as e:
1450        if e.errno != errno.ENOENT:
1451            logging.error('Problem while removing build dir: %s' % (e))
1452
1453    for dir in build_config.build_dirs:
1454        try:
1455            os.makedirs(dir)
1456        except OSError as e:
1457            if e.errno != errno.EEXIST:
1458                logging.error('Error while creating "%s": %s' % (dir, e))
1459
1460    makefile_template = os.path.join(
1461        options.makefile_dir,
1462        choose_makefile_template(template_vars['makefile_style']))
1463
1464    logging.debug('Using makefile template %s' % (makefile_template))
1465
1466    templates_to_proc = {
1467        makefile_template: template_vars['makefile_path']
1468        }
1469
1470    def templates_to_use():
1471        yield (options.build_data, 'buildh.in', 'build.h')
1472        yield (options.build_data, 'botan.doxy.in', 'botan.doxy')
1473
1474        if options.os != 'windows':
1475            yield (options.build_data, 'botan.pc.in', build_config.pkg_config_file())
1476            yield (options.build_data, 'botan-config.in', build_config.config_shell_script())
1477
1478        if options.os == 'windows':
1479            yield (options.build_data, 'innosetup.in', 'botan.iss')
1480
1481        if options.boost_python:
1482            yield (options.makefile_dir, 'python.in', 'Makefile.python')
1483
1484    for (template_dir, template, sink) in templates_to_use():
1485        source = os.path.join(template_dir, template)
1486        if template_dir == options.build_data:
1487            sink = os.path.join(build_config.build_dir, sink)
1488        templates_to_proc[source] = sink
1489
1490    for (template, sink) in templates_to_proc.items():
1491        try:
1492            f = open(sink, 'w')
1493            f.write(process_template(template, template_vars))
1494        finally:
1495            f.close()
1496
1497    link_method = choose_link_method(options.link_method)
1498    logging.info('Using %s to link files into build directory' % (link_method))
1499
1500    def link_headers(header_list, type, dir):
1501        logging.debug('Linking %d %s header files in %s' % (
1502            len(header_list), type, dir))
1503
1504        for header_file in header_list:
1505            try:
1506                portable_symlink(header_file, dir, link_method)
1507            except OSError as e:
1508                if e.errno != errno.EEXIST:
1509                    logging.error('Error linking %s into %s: %s' % (
1510                        header_file, dir, e))
1511
1512    link_headers(build_config.public_headers, 'public',
1513                 build_config.botan_include_dir)
1514
1515    link_headers(build_config.build_internal_headers, 'internal',
1516                 build_config.internal_include_dir)
1517
1518"""
1519Generate Amalgamation
1520"""
1521def generate_amalgamation(build_config):
1522    def ending_with_suffix(suffix):
1523        def predicate(val):
1524            return val.endswith(suffix)
1525        return predicate
1526
1527    def strip_header_goop(header_name, contents):
1528        header_guard = re.compile('^#define BOTAN_.*_H__$')
1529
1530        while len(contents) > 0:
1531            if header_guard.match(contents[0]):
1532                contents = contents[1:]
1533                break
1534
1535            contents = contents[1:]
1536
1537        if len(contents) == 0:
1538            raise Exception("No header guard found in " + header_name)
1539
1540        while contents[0] == '\n':
1541            contents = contents[1:]
1542
1543        while contents[-1] == '\n':
1544            contents = contents[0:-1]
1545        if contents[-1] == '#endif\n':
1546            contents = contents[0:-1]
1547
1548        return contents
1549
1550    botan_include = re.compile('#include <botan/(.*)>$')
1551    std_include = re.compile('#include <([^/\.]+)>$')
1552
1553    class Amalgamation_Generator:
1554        def __init__(self, input_list):
1555
1556            self.included_already = set()
1557            self.all_std_includes = set()
1558
1559            self.file_contents = {}
1560            for f in sorted(input_list):
1561                contents = strip_header_goop(f, open(f).readlines())
1562                self.file_contents[os.path.basename(f)] = contents
1563
1564            self.contents = ''
1565            for name in self.file_contents:
1566                self.contents += ''.join(list(self.header_contents(name)))
1567
1568            self.header_includes = ''
1569            for std_header in self.all_std_includes:
1570                self.header_includes += '#include <%s>\n' % (std_header)
1571            self.header_includes += '\n'
1572
1573        def header_contents(self, name):
1574            name = name.replace('internal/', '')
1575
1576            if name in self.included_already:
1577                return
1578
1579            self.included_already.add(name)
1580
1581            if name not in self.file_contents:
1582                return
1583
1584            for line in self.file_contents[name]:
1585                match = botan_include.search(line)
1586                if match:
1587                    for c in self.header_contents(match.group(1)):
1588                        yield c
1589                else:
1590                    match = std_include.search(line)
1591
1592                    if match and match.group(1) != 'functional':
1593                        self.all_std_includes.add(match.group(1))
1594                    else:
1595                        yield line
1596
1597    amalg_basename = 'botan_all'
1598
1599    header_name = '%s.h' % (amalg_basename)
1600
1601    botan_h = open(header_name, 'w')
1602
1603    pub_header_amalag = Amalgamation_Generator(build_config.public_headers)
1604
1605    amalg_header = """/*
1606* Botan %s Amalgamation
1607* (C) 1999-2011 Jack Lloyd and others
1608*
1609* Distributed under the terms of the Botan license
1610*/
1611""" % (build_config.version_string)
1612
1613    botan_h.write(amalg_header)
1614
1615    botan_h.write("""
1616#ifndef BOTAN_AMALGAMATION_H__
1617#define BOTAN_AMALGAMATION_H__
1618
1619""")
1620
1621    botan_h.write(pub_header_amalag.header_includes)
1622    botan_h.write(pub_header_amalag.contents)
1623    botan_h.write("\n#endif\n")
1624
1625    internal_header_amalag = Amalgamation_Generator(
1626        [s for s in build_config.internal_headers
1627         if s.find('asm_macr_') == -1])
1628
1629    botan_cpp = open('%s.cpp' % (amalg_basename), 'w')
1630
1631    botan_cpp.write(amalg_header)
1632
1633    botan_cpp.write('\n#include "%s"\n' % (header_name))
1634
1635    botan_cpp.write(internal_header_amalag.header_includes)
1636    botan_cpp.write(internal_header_amalag.contents)
1637
1638    for src in build_config.sources:
1639        if src.endswith('.S'):
1640            continue
1641
1642        contents = open(src).readlines()
1643        for line in contents:
1644            if botan_include.search(line):
1645                continue
1646            else:
1647                botan_cpp.write(line)
1648
1649"""
1650Test for the existence of a program
1651"""
1652def have_program(program):
1653
1654    def exe_test(path, program):
1655        exe_file = os.path.join(path, program)
1656
1657        if os.path.exists(exe_file) and os.access(exe_file, os.X_OK):
1658            logging.debug('Found program %s in %s' % (program, path))
1659            return True
1660        else:
1661            return False
1662
1663    exe_suffixes = ['', '.exe']
1664
1665    for path in os.environ['PATH'].split(os.pathsep):
1666        for suffix in exe_suffixes:
1667            if exe_test(path, program + suffix):
1668                return True
1669
1670    return False
1671
1672"""
1673Main driver
1674"""
1675def main(argv = None):
1676    if argv is None:
1677        argv = sys.argv
1678
1679    logging.basicConfig(stream = sys.stdout,
1680                        format = '%(levelname) 7s: %(message)s')
1681
1682    options = process_command_line(argv[1:])
1683
1684    def log_level():
1685        if options.verbose:
1686            return logging.DEBUG
1687        if options.quiet:
1688            return logging.WARNING
1689        return logging.INFO
1690
1691    logging.getLogger().setLevel(log_level())
1692
1693    logging.debug('%s invoked with options "%s"' % (
1694        argv[0], ' '.join(argv[1:])))
1695
1696    logging.debug('Platform: OS="%s" machine="%s" proc="%s"' % (
1697        platform.system(), platform.machine(), platform.processor()))
1698
1699    if options.os == "java":
1700        raise Exception("Jython detected: need --os and --cpu to set target")
1701
1702    options.base_dir = os.path.dirname(argv[0])
1703    options.src_dir = os.path.join(options.base_dir, 'src')
1704
1705    options.build_data = os.path.join(options.src_dir, 'build-data')
1706    options.makefile_dir = os.path.join(options.build_data, 'makefile')
1707
1708    (modules, archinfo, ccinfo, osinfo) = load_info_files(options)
1709
1710    if options.compiler is None:
1711        if options.os == 'windows':
1712            if have_program('g++') and not have_program('cl'):
1713                options.compiler = 'gcc'
1714            else:
1715                options.compiler = 'msvc'
1716        else:
1717            options.compiler = 'gcc'
1718        logging.info('Guessing to use compiler %s (use --cc to set)' % (
1719            options.compiler))
1720
1721    if options.os is None:
1722        options.os = platform.system().lower()
1723
1724        if re.match('^cygwin_.*', options.os):
1725            logging.debug("Converting '%s' to 'cygwin'", options.os)
1726            options.os = 'cygwin'
1727
1728        if options.os == 'windows' and options.compiler == 'gcc':
1729            logging.warning('Detected GCC on Windows; use --os=cygwin or --os=mingw?')
1730
1731        logging.info('Guessing target OS is %s (use --os to set)' % (options.os))
1732
1733    if options.compiler not in ccinfo:
1734        raise Exception('Unknown compiler "%s"; available options: %s' % (
1735            options.compiler, ' '.join(sorted(ccinfo.keys()))))
1736
1737    if options.os not in osinfo:
1738
1739        def find_canonical_os_name(os):
1740            for (name, info) in osinfo.items():
1741                if os in info.aliases:
1742                    return name
1743            return os # not found
1744
1745        options.os = find_canonical_os_name(options.os)
1746
1747        if options.os not in osinfo:
1748            raise Exception('Unknown OS "%s"; available options: %s' % (
1749                options.os, ' '.join(sorted(osinfo.keys()))))
1750
1751    if options.cpu is None:
1752        (options.arch, options.cpu) = guess_processor(archinfo)
1753        logging.info('Guessing target processor is a %s/%s (use --cpu to set)' % (
1754            options.arch, options.cpu))
1755    else:
1756        cpu_from_user = options.cpu
1757        (options.arch, options.cpu) = canon_processor(archinfo, options.cpu)
1758        logging.info('Canonicalizized --cpu=%s to %s/%s' % (
1759            cpu_from_user, options.arch, options.cpu))
1760
1761    logging.info('Target is %s-%s-%s-%s' % (
1762        options.compiler, options.os, options.arch, options.cpu))
1763
1764    cc = ccinfo[options.compiler]
1765
1766    # Kind of a hack...
1767    options.extra_flags = ''
1768    if options.compiler == 'gcc':
1769
1770        def get_gcc_version(gcc_bin):
1771            try:
1772                gcc_proc = subprocess.Popen(
1773                    gcc_bin.split(' ') + ['-dumpversion'],
1774                    stdout=subprocess.PIPE,
1775                    stderr=subprocess.PIPE,
1776                    universal_newlines=True)
1777
1778                (stdout, stderr) = gcc_proc.communicate()
1779
1780                if gcc_proc.returncode != 0:
1781                    logging.warning("GCC returned non-zero result %s" % (stderr))
1782                    return None
1783
1784                gcc_version = stdout.strip()
1785
1786                logging.info('Detected gcc version %s' % (gcc_version))
1787                return [int(v) for v in gcc_version.split('.')]
1788            except OSError:
1789                logging.warning('Could not execute %s for version check' % (gcc_bin))
1790                return None
1791
1792        def is_64bit_arch(arch):
1793            if arch.endswith('64') or arch in ['alpha', 's390x']:
1794                return True
1795            return False
1796
1797        gcc_version = get_gcc_version(options.compiler_binary or cc.binary_name)
1798
1799        def gcc_version_matches(matches):
1800            for match in matches.items():
1801                if gcc_version[0] != match[0]:
1802                    continue
1803
1804                for minor in match[1]:
1805                    if minor == gcc_version[1]:
1806                        return True
1807            return False
1808
1809        if gcc_version:
1810
1811            if not is_64bit_arch(options.arch) and not options.dumb_gcc:
1812                if gcc_version_matches({ 4 : [0, 1, 2, 3, 4], 3 : [3, 4], 2 : [95] }):
1813                    options.dumb_gcc = True
1814
1815            if options.with_tr1 == None and \
1816                    gcc_version_matches({ 4 : [0], 3 : [0,1,2,3,4], 2 : [95] }):
1817                logging.info('Disabling TR1 support for this gcc, too old')
1818                options.with_tr1 = 'none'
1819
1820            if options.with_visibility == None and \
1821                    gcc_version_matches({ 3 : [0,1,2,3,4], 2 : [95] }):
1822                logging.info('Disabling DSO visibility support for this gcc, too old')
1823                options.with_visibility = False
1824
1825        if options.dumb_gcc is True:
1826            logging.info('Setting -fpermissive to work around gcc bug')
1827            options.extra_flags = ' -fpermissive'
1828
1829    if options.with_visibility is None:
1830        options.with_visibility = True
1831
1832    if options.with_tr1 == None:
1833        if cc.has_tr1:
1834            logging.info('Assuming %s has TR1 (use --with-tr1=none to disable)' % (
1835                options.compiler))
1836            options.with_tr1 = 'system'
1837        else:
1838            options.with_tr1 = 'none'
1839
1840    if options.with_sphinx is None:
1841        if have_program('sphinx-build'):
1842            logging.info('Found sphinx-build, will use it ' +
1843                         '(use --without-sphinx to disable)')
1844            options.with_sphinx = True
1845
1846    if options.via_amalgamation:
1847        options.gen_amalgamation = True
1848
1849    if options.gen_amalgamation:
1850        if options.asm_ok:
1851            logging.info('Disabling assembly code, cannot use in amalgamation')
1852            options.asm_ok = False
1853
1854    modules_to_use = choose_modules_to_use(modules,
1855                                           archinfo[options.arch],
1856                                           options)
1857
1858    if not osinfo[options.os].build_shared:
1859        if options.build_shared_lib:
1860            logging.info('Disabling shared lib on %s' % (options.os))
1861            options.build_shared_lib = False
1862
1863    build_config = BuildConfigurationInformation(options, modules_to_use)
1864    build_config.public_headers.append(
1865        os.path.join(build_config.build_dir, 'build.h'))
1866
1867    template_vars = create_template_vars(build_config, options,
1868                                         modules_to_use,
1869                                         cc,
1870                                         archinfo[options.arch],
1871                                         osinfo[options.os])
1872
1873    # Performs the I/O
1874    setup_build(build_config, options, template_vars)
1875
1876    if options.gen_amalgamation:
1877        generate_amalgamation(build_config)
1878
1879    logging.info('Botan %s build setup is complete' % (
1880        build_config.version_string))
1881
1882if __name__ == '__main__':
1883    try:
1884        main()
1885    except Exception as e:
1886        logging.error(str(e))
1887        import traceback
1888        logging.debug(traceback.format_exc())
1889        sys.exit(1)
1890    sys.exit(0)
1891