aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/botan/configure.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/3rdparty/botan/configure.py')
-rwxr-xr-x[-rw-r--r--]src/libs/3rdparty/botan/configure.py3876
1 files changed, 2585 insertions, 1291 deletions
diff --git a/src/libs/3rdparty/botan/configure.py b/src/libs/3rdparty/botan/configure.py
index 71d2a3d39d..b09f07bb5e 100644..100755
--- a/src/libs/3rdparty/botan/configure.py
+++ b/src/libs/3rdparty/botan/configure.py
@@ -1,22 +1,27 @@
#!/usr/bin/env python
"""
-Configuration program for botan (http://botan.randombit.net/)
- (C) 2009-2011 Jack Lloyd
- Distributed under the terms of the Botan license
+Configuration program for botan
-Tested with CPython 2.6, 2.7, 3.1 and PyPy 1.5
+(C) 2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 Jack Lloyd
+(C) 2015,2016,2017 Simon Warta (Kullo GmbH)
-Python 2.5 works if you change the exception catching syntax:
- perl -pi -e 's/except (.*) as (.*):/except $1, $2:/g' configure.py
+Botan is released under the Simplified BSD License (see license.txt)
-Jython - Target detection does not work (use --os and --cpu)
+This script is regularly tested with CPython 2.7 and 3.5, and
+occasionally tested with CPython 2.6 and PyPy 4.
-CPython 2.4 and earlier are not supported
+Support for CPython 2.6 will be dropped eventually, but is kept up for as
+long as reasonably convenient.
-Has not been tested with IronPython
+CPython 2.5 and earlier are not supported.
+
+On Jython target detection does not work (use --os and --cpu).
"""
+import collections
+import copy
+import json
import sys
import os
import os.path
@@ -24,322 +29,485 @@ import platform
import re
import shlex
import shutil
-import string
import subprocess
+import traceback
import logging
-import getpass
import time
import errno
-import optparse
+import optparse # pylint: disable=deprecated-module
+
+# An error caused by and to be fixed by the user, e.g. invalid command line argument
+class UserError(Exception):
+ pass
+
-# Avoid useless botan_version.pyc (Python 2.6 or higher)
-if 'dont_write_bytecode' in sys.__dict__:
- sys.dont_write_bytecode = True
+# An error caused by bugs in this script or when reading/parsing build data files
+# Those are not expected to be fixed by the user of this script
+class InternalError(Exception):
+ pass
-import botan_version
def flatten(l):
return sum(l, [])
-def get_vc_revision():
- try:
- mtn = subprocess.Popen(['mtn', 'automate', 'heads'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True)
+def normalize_source_path(source):
+ """
+ cmake needs this, and nothing else minds
+ """
+ return os.path.normpath(source).replace('\\', '/')
- (stdout, stderr) = mtn.communicate()
+def parse_version_file(version_path):
+ version_file = open(version_path)
+ key_and_val = re.compile(r"([a-z_]+) = ([a-zA-Z0-9:\-\']+)")
- if mtn.returncode != 0:
- logging.debug('Error getting rev from monotone - %d (%s)'
- % (mtn.returncode, stderr))
- return 'unknown'
+ results = {}
+ for line in version_file.readlines():
+ if not line or line[0] == '#':
+ continue
+ match = key_and_val.match(line)
+ if match:
+ key = match.group(1)
+ val = match.group(2)
+
+ if val == 'None':
+ val = None
+ elif val.startswith("'") and val.endswith("'"):
+ val = val[1:len(val)-1]
+ else:
+ val = int(val)
- rev = str(stdout).strip()
- logging.debug('Monotone reported revision %s' % (rev))
+ results[key] = val
+ return results
- return 'mtn:' + rev
- except OSError as e:
- logging.debug('Error getting rev from monotone - %s' % (e[1]))
- return 'unknown'
- except Exception as e:
- logging.debug('Error getting rev from monotone - %s' % (e))
- return 'unknown'
+class Version(object):
+ """
+ Version information are all static members
+ """
+ data = {}
+
+ @staticmethod
+ def get_data():
+ if not Version.data:
+ root_dir = os.path.dirname(os.path.realpath(__file__))
+ Version.data = parse_version_file(os.path.join(root_dir, 'src/build-data/version.txt'))
+ return Version.data
+
+ @staticmethod
+ def major():
+ return Version.get_data()["release_major"]
+
+ @staticmethod
+ def minor():
+ return Version.get_data()["release_minor"]
+
+ @staticmethod
+ def patch():
+ return Version.get_data()["release_patch"]
+
+ @staticmethod
+ def packed():
+ # Used on Darwin for dylib versioning
+ return Version.major() * 1000 + Version.minor()
+
+ @staticmethod
+ def so_rev():
+ return Version.get_data()["release_so_abi_rev"]
+
+ @staticmethod
+ def release_type():
+ return Version.get_data()["release_type"]
+
+ @staticmethod
+ def datestamp():
+ return Version.get_data()["release_datestamp"]
+
+ @staticmethod
+ def as_string():
+ return '%d.%d.%d' % (Version.major(), Version.minor(), Version.patch())
+
+ @staticmethod
+ def vc_rev():
+ # Lazy load to ensure _local_repo_vc_revision() does not run before logger is set up
+ if Version.get_data()["release_vc_rev"] is None:
+ Version.data["release_vc_rev"] = Version._local_repo_vc_revision()
+ return Version.get_data()["release_vc_rev"]
+
+ @staticmethod
+ def _local_repo_vc_revision():
+ vc_command = ['git', 'rev-parse', 'HEAD']
+ cmdname = vc_command[0]
+
+ try:
+ vc = subprocess.Popen(
+ vc_command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+ (stdout, stderr) = vc.communicate()
+
+ if vc.returncode != 0:
+ logging.debug('Error getting rev from %s - %d (%s)'
+ % (cmdname, vc.returncode, stderr))
+ return 'unknown'
+
+ rev = str(stdout).strip()
+ logging.debug('%s reported revision %s' % (cmdname, rev))
+
+ return '%s:%s' % (cmdname, rev)
+ except OSError as e:
+ logging.debug('Error getting rev from %s - %s' % (cmdname, e.strerror))
+ return 'unknown'
-class BuildConfigurationInformation(object):
+class SourcePaths(object):
"""
- Version information
+ A collection of paths defined by the project structure and
+ independent of user configurations.
+ All paths are relative to the base_dir, which may be relative as well (e.g. ".")
"""
- version_major = botan_version.release_major
- version_minor = botan_version.release_minor
- version_patch = botan_version.release_patch
- version_so_rev = botan_version.release_so_abi_rev
- version_datestamp = botan_version.release_datestamp
+ def __init__(self, base_dir):
+ self.base_dir = base_dir
+ self.doc_dir = os.path.join(self.base_dir, 'doc')
+ self.src_dir = os.path.join(self.base_dir, 'src')
+
+ # dirs in src/
+ self.build_data_dir = os.path.join(self.src_dir, 'build-data')
+ self.configs_dir = os.path.join(self.src_dir, 'configs')
+ self.lib_dir = os.path.join(self.src_dir, 'lib')
+ self.python_dir = os.path.join(self.src_dir, 'python')
+ self.scripts_dir = os.path.join(self.src_dir, 'scripts')
+
+ # subdirs of src/
+ self.sphinx_config_dir = os.path.join(self.configs_dir, 'sphinx')
- version_vc_rev = botan_version.release_vc_rev
- version_string = '%d.%d.%d' % (version_major, version_minor, version_patch)
+class BuildPaths(object): # pylint: disable=too-many-instance-attributes
"""
Constructor
"""
- def __init__(self, options, modules):
-
- if self.version_vc_rev is None:
- self.version_vc_rev = get_vc_revision()
-
+ def __init__(self, source_paths, options, modules):
self.build_dir = os.path.join(options.with_build_dir, 'build')
- self.checkobj_dir = os.path.join(self.build_dir, 'checks')
- self.libobj_dir = os.path.join(self.build_dir, 'lib')
-
- self.python_dir = os.path.join(options.src_dir, 'wrap', 'python')
-
- self.boost_python = options.boost_python
+ self.libobj_dir = os.path.join(self.build_dir, 'obj', 'lib')
+ self.cliobj_dir = os.path.join(self.build_dir, 'obj', 'cli')
+ self.testobj_dir = os.path.join(self.build_dir, 'obj', 'test')
self.doc_output_dir = os.path.join(self.build_dir, 'docs')
-
- self.pyobject_dir = os.path.join(self.build_dir, 'python')
+ self.doc_output_dir_manual = os.path.join(self.doc_output_dir, 'manual')
+ self.doc_output_dir_doxygen = os.path.join(self.doc_output_dir, 'doxygen') if options.with_doxygen else None
self.include_dir = os.path.join(self.build_dir, 'include')
self.botan_include_dir = os.path.join(self.include_dir, 'botan')
self.internal_include_dir = os.path.join(self.botan_include_dir, 'internal')
+ self.external_include_dir = os.path.join(self.include_dir, 'external')
- self.sources = sorted(flatten([mod.sources() for mod in modules]))
self.internal_headers = sorted(flatten([m.internal_headers() for m in modules]))
+ self.external_headers = sorted(flatten([m.external_headers() for m in modules]))
- if options.via_amalgamation:
- self.build_sources = ['botan_all.cpp']
- self.build_internal_headers = []
+ if options.amalgamation:
+ self.lib_sources = ['botan_all.cpp']
else:
- self.build_sources = self.sources
- self.build_internal_headers = self.internal_headers
+ self.lib_sources = [normalize_source_path(s) for s in sorted(flatten([mod.sources() for mod in modules]))]
self.public_headers = sorted(flatten([m.public_headers() for m in modules]))
- checks_dir = os.path.join(options.base_dir, 'checks')
-
- self.check_sources = sorted(
- [os.path.join(checks_dir, file) for file in os.listdir(checks_dir)
- if file.endswith('.cpp')])
-
- self.python_sources = sorted(
- [os.path.join(self.python_dir, file)
- for file in os.listdir(self.python_dir)
- if file.endswith('.cpp')])
-
- self.manual_dir = os.path.join(self. doc_output_dir, 'manual')
+ def find_sources_in(basedir, srcdir):
+ for (dirpath, _, filenames) in os.walk(os.path.join(basedir, srcdir)):
+ for filename in filenames:
+ if filename.endswith('.cpp') and not filename.startswith('.'):
+ yield os.path.join(dirpath, filename)
+
+ def find_headers_in(basedir, srcdir):
+ for (dirpath, _, filenames) in os.walk(os.path.join(basedir, srcdir)):
+ for filename in filenames:
+ if filename.endswith('.h') and not filename.startswith('.'):
+ yield os.path.join(dirpath, filename)
+
+ self.cli_sources = [normalize_source_path(s) for s in find_sources_in(source_paths.src_dir, 'cli')]
+ self.cli_headers = [normalize_source_path(s) for s in find_headers_in(source_paths.src_dir, 'cli')]
+ self.test_sources = [normalize_source_path(s) for s in find_sources_in(source_paths.src_dir, 'tests')]
+
+ if options.build_fuzzers:
+ self.fuzzer_sources = list(find_sources_in(source_paths.src_dir, 'fuzzer'))
+ self.fuzzer_output_dir = os.path.join(self.build_dir, 'fuzzer')
+ self.fuzzobj_dir = os.path.join(self.build_dir, 'obj', 'fuzzer')
+ else:
+ self.fuzzer_sources = None
+ self.fuzzer_output_dir = None
+ self.fuzzobj_dir = None
+
+ def build_dirs(self):
+ out = [
+ self.libobj_dir,
+ self.cliobj_dir,
+ self.testobj_dir,
+ self.botan_include_dir,
+ self.internal_include_dir,
+ self.external_include_dir,
+ self.doc_output_dir_manual,
+ ]
+ if self.doc_output_dir_doxygen:
+ out += [self.doc_output_dir_doxygen]
+ if self.fuzzer_output_dir:
+ out += [self.fuzzobj_dir]
+ out += [self.fuzzer_output_dir]
+ return out
+
+ def format_include_paths(self, cc, external_include):
+ dash_i = cc.add_include_dir_option
+ output = dash_i + self.include_dir
+ if self.external_headers:
+ output += ' ' + dash_i + self.external_include_dir
+ if external_include:
+ output += ' ' + dash_i + external_include
+ return output
+
+ def src_info(self, typ):
+ if typ == 'lib':
+ return (self.lib_sources, self.libobj_dir)
+ elif typ == 'cli':
+ return (self.cli_sources, self.cliobj_dir)
+ elif typ == 'test':
+ return (self.test_sources, self.testobj_dir)
+ elif typ == 'fuzzer':
+ return (self.fuzzer_sources, self.fuzzobj_dir)
+ else:
+ raise InternalError("Unknown src info type '%s'" % (typ))
- def build_doc_commands():
- yield '$(COPY) readme.txt %s' % (self.doc_output_dir)
+def process_command_line(args): # pylint: disable=too-many-locals
+ """
+ Handle command line options
+ Do not use logging in this method as command line options need to be
+ available before logging is setup.
+ """
- if options.with_sphinx:
- yield 'sphinx-build $(SPHINX_OPTS) -b html doc %s' % (
- self.manual_dir)
- else:
- yield '$(COPY) doc/*.txt %s' % (self.manual_dir)
+ parser = optparse.OptionParser(
+ formatter=optparse.IndentedHelpFormatter(max_help_position=50),
+ version=Version.as_string())
- if options.with_doxygen:
- yield 'doxygen %s/botan.doxy' % (self.build_dir)
+ parser.add_option('--verbose', action='store_true', default=False,
+ help='Show debug messages')
+ parser.add_option('--quiet', action='store_true', default=False,
+ help='Show only warnings and errors')
- self.build_doc_commands = '\n'.join(['\t' + s for s in build_doc_commands()])
+ target_group = optparse.OptionGroup(parser, 'Target options')
- def build_dirs():
- yield self.checkobj_dir
- yield self.libobj_dir
- yield self.botan_include_dir
- yield self.internal_include_dir
- yield os.path.join(self.doc_output_dir, 'manual')
- if options.with_doxygen:
- yield os.path.join(self.doc_output_dir, 'doxygen')
+ target_group.add_option('--cpu', help='set the target CPU architecture')
- if self.boost_python:
- yield self.pyobject_dir
+ target_group.add_option('--os', help='set the target operating system')
- self.build_dirs = list(build_dirs())
+ target_group.add_option('--cc', dest='compiler', help='set the desired build compiler')
- def pkg_config_file(self):
- return 'botan-%d.%d.pc' % (self.version_major,
- self.version_minor)
+ target_group.add_option('--cc-min-version', dest='cc_min_version', default=None,
+ metavar='MAJOR.MINOR',
+ help='Set the minimal version of the target compiler. ' \
+ 'Use --cc-min-version=0.0 to support all compiler versions. ' \
+ 'Default is auto detection.')
- def config_shell_script(self):
- return 'botan-config-%d.%d' % (self.version_major,
- self.version_minor)
+ target_group.add_option('--cc-bin', dest='compiler_binary', metavar='BINARY',
+ help='set path to compiler binary')
- def username(self):
- return getpass.getuser()
+ target_group.add_option('--cc-abi-flags', metavar='FLAG', default='',
+ help='set compiler ABI flags')
- def hostname(self):
- return platform.node()
+ target_group.add_option('--cxxflags', metavar='FLAG', default=None,
+ help='set compiler flags')
- def timestamp(self):
- return time.ctime()
+ target_group.add_option('--ldflags', metavar='FLAG',
+ help='set linker flags', default=None)
-"""
-Handle command line options
-"""
-def process_command_line(args):
+ target_group.add_option('--ar-command', dest='ar_command', metavar='AR', default=None,
+ help='set path to static archive creator')
- parser = optparse.OptionParser(
- formatter = optparse.IndentedHelpFormatter(max_help_position = 50),
- version = BuildConfigurationInformation.version_string)
+ target_group.add_option('--msvc-runtime', metavar='RT', default=None,
+ help='specify MSVC runtime (MT, MD, MTd, MDd)')
- parser.add_option('--verbose', action='store_true', default=False,
- help='Show debug messages')
- parser.add_option('--quiet', action='store_true', default=False,
- help='Show only warnings and errors')
+ target_group.add_option('--with-endian', metavar='ORDER', default=None,
+ help='override byte order guess')
- target_group = optparse.OptionGroup(parser, 'Target options')
+ target_group.add_option('--with-os-features', action='append', metavar='FEAT',
+ help='specify OS features to use')
+ target_group.add_option('--without-os-features', action='append', metavar='FEAT',
+ help='specify OS features to disable')
- target_group.add_option('--cpu',
- help='set the target processor type/model')
+ isa_extensions = [
+ 'SSE2', 'SSSE3', 'SSE4.1', 'SSE4.2', 'AVX2',
+ 'AES-NI', 'SHA',
+ 'AltiVec', 'NEON', 'ARMv8Crypto']
- target_group.add_option('--os',
- help='set the target operating system')
+ for isa_extn_name in isa_extensions:
+ isa_extn = isa_extn_name.lower()
- target_group.add_option('--cc', dest='compiler',
- help='set the desired build compiler')
+ target_group.add_option('--disable-%s' % (isa_extn),
+ help='disable %s intrinsics' % (isa_extn_name),
+ action='append_const',
+ const=isa_extn.replace('-', '').replace('.', ''),
+ dest='disable_intrinsics')
- target_group.add_option('--cc-bin', dest='compiler_binary',
- metavar='BINARY',
- help='set the name of the compiler binary')
+ build_group = optparse.OptionGroup(parser, 'Build options')
- target_group.add_option('--with-endian', metavar='ORDER', default=None,
- help='override guess of CPU byte order')
+ build_group.add_option('--with-debug-info', action='store_true', default=False, dest='with_debug_info',
+ help='include debug symbols')
- target_group.add_option('--with-unaligned-mem',
- dest='unaligned_mem', action='store_true',
- default=None,
- help='enable unaligned memory accesses')
+ build_group.add_option('--with-sanitizers', action='store_true', default=False, dest='with_sanitizers',
+ help='enable ASan/UBSan checks')
- target_group.add_option('--without-unaligned-mem',
- dest='unaligned_mem', action='store_false',
- help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--enable-sanitizers', metavar='SAN', default='',
+ help='enable specific sanitizers')
- for isa_extn_name in ['SSE2', 'SSSE3', 'AltiVec', 'AES-NI', 'movbe']:
- isa_extn = isa_extn_name.lower()
+ build_group.add_option('--with-stack-protector', dest='with_stack_protector',
+ action='store_false', default=None, help=optparse.SUPPRESS_HELP)
- target_group.add_option('--enable-%s' % (isa_extn),
- help='enable use of %s' % (isa_extn_name),
- action='append_const',
- const=isa_extn,
- dest='enable_isa_extns')
+ build_group.add_option('--without-stack-protector', dest='with_stack_protector',
+ action='store_false', help='disable stack smashing protections')
- target_group.add_option('--disable-%s' % (isa_extn),
- help=optparse.SUPPRESS_HELP,
- action='append_const',
- const=isa_extn,
- dest='disable_isa_extns')
+ build_group.add_option('--with-coverage', action='store_true', default=False, dest='with_coverage',
+ help='add coverage info and disable opts')
- build_group = optparse.OptionGroup(parser, 'Build options')
+ build_group.add_option('--with-coverage-info', action='store_true', default=False, dest='with_coverage_info',
+ help='add coverage info')
- build_group.add_option('--enable-shared', dest='build_shared_lib',
- action='store_true', default=True,
- help=optparse.SUPPRESS_HELP)
- build_group.add_option('--disable-shared', dest='build_shared_lib',
+ build_group.add_option('--enable-shared-library', dest='build_shared_lib',
+ action='store_true', default=None,
+ help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--disable-shared-library', dest='build_shared_lib',
action='store_false',
- help='disable building a shared library')
+ help='disable building shared library')
- build_group.add_option('--enable-asm', dest='asm_ok',
- action='store_true', default=True,
+ build_group.add_option('--enable-static-library', dest='build_static_lib',
+ action='store_true', default=None,
help=optparse.SUPPRESS_HELP)
- build_group.add_option('--disable-asm', dest='asm_ok',
+ build_group.add_option('--disable-static-library', dest='build_static_lib',
action='store_false',
- help='disallow use of assembler')
+ help='disable building static library')
- build_group.add_option('--enable-debug', dest='debug_build',
+ build_group.add_option('--optimize-for-size', dest='optimize_for_size',
action='store_true', default=False,
- help='enable debug build')
- build_group.add_option('--disable-debug', dest='debug_build',
- action='store_false', help=optparse.SUPPRESS_HELP)
+ help='optimize for code size')
build_group.add_option('--no-optimizations', dest='no_optimizations',
action='store_true', default=False,
- help=optparse.SUPPRESS_HELP)
+ help='disable all optimizations (for debugging)')
- build_group.add_option('--gen-amalgamation', dest='gen_amalgamation',
- default=False, action='store_true',
- help='generate amalgamation files')
+ build_group.add_option('--debug-mode', action='store_true', default=False, dest='debug_mode',
+ help='enable debug info, disable optimizations')
- build_group.add_option('--via-amalgamation', dest='via_amalgamation',
+ build_group.add_option('--amalgamation', dest='amalgamation',
default=False, action='store_true',
- help='build via amalgamation')
+ help='use amalgamation to build')
- build_group.add_option('--with-tr1-implementation', metavar='WHICH',
- dest='with_tr1', default=None,
- help='enable TR1 (choices: none, system, boost)')
+ build_group.add_option('--single-amalgamation-file',
+ default=False, action='store_true',
+ help='build single file instead of splitting on ABI')
- build_group.add_option('--with-build-dir',
- metavar='DIR', default='',
+ build_group.add_option('--with-build-dir', metavar='DIR', default='',
help='setup the build in DIR')
- build_group.add_option('--makefile-style', metavar='STYLE', default=None,
- help='choose a makefile style (unix or nmake)')
+ build_group.add_option('--with-external-includedir', metavar='DIR', default='',
+ help='use DIR for external includes')
+
+ build_group.add_option('--with-external-libdir', metavar='DIR', default='',
+ help='use DIR for external libs')
+
+ build_group.add_option('--with-sysroot-dir', metavar='DIR', default='',
+ help='use DIR for system root while cross-compiling')
+
+ build_group.add_option('--with-openmp', default=False, action='store_true',
+ help='enable use of OpenMP')
+
+ link_methods = ['symlink', 'hardlink', 'copy']
+ build_group.add_option('--link-method', default=None, metavar='METHOD',
+ choices=link_methods,
+ help='choose how links to include headers are created (%s)' % ', '.join(link_methods))
build_group.add_option('--with-local-config',
dest='local_config', metavar='FILE',
help='include the contents of FILE into build.h')
build_group.add_option('--distribution-info', metavar='STRING',
- help='set distribution specific versioning',
+ help='distribution specific version',
default='unspecified')
- build_group.add_option('--with-sphinx', action='store_true',
- default=None,
- help='Use Sphinx to generate HTML manual')
+ build_group.add_option('--maintainer-mode', dest='maintainer_mode',
+ action='store_true', default=False,
+ help="Enable extra warnings")
- build_group.add_option('--without-sphinx', action='store_false',
- dest='with_sphinx', help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--with-python-versions', dest='python_version',
+ metavar='N.M',
+ default='%d.%d' % (sys.version_info[0], sys.version_info[1]),
+ help='where to install botan2.py (def %default)')
- build_group.add_option('--with-visibility', action='store_true',
- default=None, help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--disable-cc-tests', dest='enable_cc_tests',
+ default=True, action='store_false',
+ help=optparse.SUPPRESS_HELP)
- build_group.add_option('--without-visibility', action='store_false',
- dest='with_visibility', help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--with-valgrind', help='use valgrind API',
+ dest='with_valgrind', action='store_true', default=False)
- build_group.add_option('--with-doxygen', action='store_true',
- default=False,
- help='Use Doxygen to generate HTML API docs')
+ # Cmake and bakefile options are hidden as they should not be used by end users
+ build_group.add_option('--with-cmake', action='store_true',
+ default=False, help=optparse.SUPPRESS_HELP)
- build_group.add_option('--without-doxygen', action='store_false',
- dest='with_doxygen', help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--with-bakefile', action='store_true',
+ default=False, help=optparse.SUPPRESS_HELP)
- build_group.add_option('--dumb-gcc', dest='dumb_gcc',
- action='store_true', default=False,
- help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--unsafe-fuzzer-mode', action='store_true', default=False,
+ help='Disable essential checks for testing')
- build_group.add_option('--maintainer-mode', dest='maintainer_mode',
- action='store_true', default=False,
- help=optparse.SUPPRESS_HELP)
+ build_group.add_option('--build-fuzzers', dest='build_fuzzers',
+ metavar='TYPE', default=None,
+ help='Build fuzzers (afl, libfuzzer, klee, test)')
- build_group.add_option('--dirty-tree', dest='clean_build_tree',
- action='store_false', default=True,
+ build_group.add_option('--with-fuzzer-lib', metavar='LIB', default=None, dest='fuzzer_lib',
+ help='additionally link in LIB')
+
+ build_group.add_option('--test-mode', action='store_true', default=False,
help=optparse.SUPPRESS_HELP)
- build_group.add_option('--link-method',
- default=None,
+ build_group.add_option('--with-debug-asserts', action='store_true', default=False,
help=optparse.SUPPRESS_HELP)
- wrapper_group = optparse.OptionGroup(parser, 'Wrapper options')
+ docs_group = optparse.OptionGroup(parser, 'Documentation Options')
+
+ docs_group.add_option('--with-documentation', action='store_true',
+ help=optparse.SUPPRESS_HELP)
+
+ docs_group.add_option('--without-documentation', action='store_false',
+ default=True, dest='with_documentation',
+ help='Skip building/installing documentation')
+
+ docs_group.add_option('--with-sphinx', action='store_true',
+ default=None, help='Use Sphinx')
+
+ docs_group.add_option('--without-sphinx', action='store_false',
+ dest='with_sphinx', help=optparse.SUPPRESS_HELP)
+
+ docs_group.add_option('--with-pdf', action='store_true',
+ default=False, help='Use Sphinx to generate PDF doc')
- wrapper_group.add_option('--with-boost-python', dest='boost_python',
- default=False, action='store_true',
- help='enable Boost.Python wrapper')
+ docs_group.add_option('--without-pdf', action='store_false',
+ dest='with_pdf', help=optparse.SUPPRESS_HELP)
- wrapper_group.add_option('--without-boost-python',
- dest='boost_python',
- action='store_false',
- help=optparse.SUPPRESS_HELP)
+ docs_group.add_option('--with-rst2man', action='store_true',
+ default=None, help='Use rst2man to generate man page')
- wrapper_group.add_option('--with-python-version', dest='python_version',
- metavar='N.M',
- default='.'.join(map(str, sys.version_info[0:2])),
- help='specify Python to build against (eg %default)')
+ docs_group.add_option('--without-rst2man', action='store_false',
+ dest='with_rst2man', help=optparse.SUPPRESS_HELP)
+
+ docs_group.add_option('--with-doxygen', action='store_true',
+ default=False, help='Use Doxygen')
+
+ docs_group.add_option('--without-doxygen', action='store_false',
+ dest='with_doxygen', help=optparse.SUPPRESS_HELP)
mods_group = optparse.OptionGroup(parser, 'Module selection')
+ mods_group.add_option('--module-policy', dest='module_policy',
+ help="module policy file (see src/build-data/policy)",
+ metavar='POL', default=None)
+
mods_group.add_option('--enable-modules', dest='enabled_modules',
metavar='MODS', action='append',
help='enable specific modules')
@@ -347,14 +515,16 @@ def process_command_line(args):
metavar='MODS', action='append',
help='disable specific modules')
mods_group.add_option('--no-autoload', action='store_true', default=False,
- help='disable automatic loading')
+ help=optparse.SUPPRESS_HELP)
+ mods_group.add_option('--minimized-build', action='store_true', dest='no_autoload',
+ help='minimize build')
- for lib in ['OpenSSL', 'GNU MP', 'Bzip2', 'Zlib']:
-
- mod = lib.lower().replace(' ', '')
+ # Should be derived from info.txt but this runs too early
+ third_party = ['bearssl', 'boost', 'bzip2', 'lzma', 'openssl', 'sqlite3', 'zlib', 'tpm']
+ for mod in third_party:
mods_group.add_option('--with-%s' % (mod),
- help='add support for using %s' % (lib),
+ help=('use %s' % (mod)) if mod in third_party else optparse.SUPPRESS_HELP,
action='append_const',
const=mod,
dest='enabled_modules')
@@ -365,26 +535,48 @@ def process_command_line(args):
const=mod,
dest='disabled_modules')
+ mods_group.add_option('--with-everything', help=optparse.SUPPRESS_HELP,
+ action='store_true', default=False)
+
install_group = optparse.OptionGroup(parser, 'Installation options')
+ install_group.add_option('--program-suffix', metavar='SUFFIX',
+ help='append string to program names')
+ install_group.add_option('--library-suffix', metavar='SUFFIX', default='',
+ help='append string to library names')
+
install_group.add_option('--prefix', metavar='DIR',
- help='set the base install directory')
+ help='set the install prefix')
install_group.add_option('--docdir', metavar='DIR',
- help='set the documentation install directory')
+ help='set the doc install dir')
+ install_group.add_option('--bindir', metavar='DIR',
+ help='set the binary install dir')
install_group.add_option('--libdir', metavar='DIR',
- help='set the library install directory')
+ help='set the library install dir')
+ install_group.add_option('--mandir', metavar='DIR',
+ help='set the install dir for man pages')
install_group.add_option('--includedir', metavar='DIR',
- help='set the include file install directory')
+ help='set the include file install dir')
+
+ info_group = optparse.OptionGroup(parser, 'Informational')
+
+ info_group.add_option('--list-modules', dest='list_modules',
+ action='store_true',
+ help='list available modules and exit')
+
+ info_group.add_option('--list-os-features', dest='list_os_features',
+ action='store_true',
+ help='list available OS features and exit')
parser.add_option_group(target_group)
parser.add_option_group(build_group)
+ parser.add_option_group(docs_group)
parser.add_option_group(mods_group)
- parser.add_option_group(wrapper_group)
parser.add_option_group(install_group)
+ parser.add_option_group(info_group)
- # These exist only for autoconf compatability (requested by zw for mtn)
+ # These exist only for autoconf compatibility (requested by zw for mtn)
compat_with_autoconf_options = [
- 'bindir',
'datadir',
'datarootdir',
'dvidir',
@@ -394,7 +586,6 @@ def process_command_line(args):
'libexecdir',
'localedir',
'localstatedir',
- 'mandir',
'oldincludedir',
'pdfdir',
'psdir',
@@ -409,11 +600,18 @@ def process_command_line(args):
(options, args) = parser.parse_args(args)
if args != []:
- raise Exception('Unhandled option(s): ' + ' '.join(args))
- if options.with_endian != None and \
- options.with_endian not in ['little', 'big']:
- raise Exception('Bad value to --with-endian "%s"' % (
- options.with_endian))
+ raise UserError('Unhandled option(s): ' + ' '.join(args))
+
+ if options.with_endian not in [None, 'little', 'big']:
+ raise UserError('Bad value to --with-endian "%s"' % (options.with_endian))
+
+ if options.debug_mode:
+ options.no_optimizations = True
+ options.with_debug_info = True
+
+ if options.with_coverage:
+ options.with_coverage_info = True
+ options.no_optimizations = True
def parse_multiple_enable(modules):
if modules is None:
@@ -423,78 +621,78 @@ def process_command_line(args):
options.enabled_modules = parse_multiple_enable(options.enabled_modules)
options.disabled_modules = parse_multiple_enable(options.disabled_modules)
- options.enable_isa_extns = parse_multiple_enable(options.enable_isa_extns)
- options.disable_isa_extns = parse_multiple_enable(options.disable_isa_extns)
+ options.with_os_features = parse_multiple_enable(options.with_os_features)
+ options.without_os_features = parse_multiple_enable(options.without_os_features)
- def enabled_or_disabled_isa(isa):
- if isa in options.enable_isa_extns:
- return True
- if isa in options.disable_isa_extns:
- return True
- return False
-
- isa_deps = {
- 'ssse3': 'sse2',
- 'aes-ni': 'sse2'
- }
+ options.disable_intrinsics = parse_multiple_enable(options.disable_intrinsics)
- if 'sse2' in options.disable_isa_extns:
- for isa in [k for (k,v) in isa_deps.items() if v == 'sse2']:
- # If explicitly enabled, allow it even if a dependency
- # violation; trust the user to know what they want
- if not enabled_or_disabled_isa(isa):
- options.disable_isa_extns.append(isa)
+ # Take some values from environment, if not set on command line
- for isa in options.enable_isa_extns:
- if isa in isa_deps:
- for dep in isa_deps.get(isa, '').split(','):
- if not enabled_or_disabled_isa(dep):
- options.enable_isa_extns.append(dep)
+ if options.ar_command is None:
+ options.ar_command = os.getenv('AR')
+ if options.compiler_binary is None:
+ options.compiler_binary = os.getenv('CXX')
+ if options.cxxflags is None:
+ options.cxxflags = os.getenv('CXXFLAGS')
+ if options.ldflags is None:
+ options.ldflags = os.getenv('LDFLAGS')
return options
-"""
-Generic lexer function for info.txt and src/build-data files
-"""
-def lex_me_harder(infofile, to_obj, allowed_groups, name_val_pairs):
- # Format as a nameable Python variable
- def py_var(group):
- return group.replace(':', '_')
+class LexResult(object):
+ pass
- class LexerError(Exception):
- def __init__(self, msg, line):
- self.msg = msg
- self.line = line
- def __str__(self):
- return '%s at %s:%d' % (self.msg, infofile, self.line)
+class LexerError(InternalError):
+ def __init__(self, msg, lexfile, line):
+ super(LexerError, self).__init__(msg)
+ self.msg = msg
+ self.lexfile = lexfile
+ self.line = line
- (dirname, basename) = os.path.split(infofile)
+ def __str__(self):
+ return '%s at %s:%d' % (self.msg, self.lexfile, self.line)
- to_obj.lives_in = dirname
- if basename == 'info.txt':
- (obj_dir,to_obj.basename) = os.path.split(dirname)
- if os.access(os.path.join(obj_dir, 'info.txt'), os.R_OK):
- to_obj.parent_module = os.path.basename(obj_dir)
- else:
- to_obj.parent_module = None
- else:
- to_obj.basename = basename.replace('.txt', '')
+
+def parse_lex_dict(as_list):
+ if len(as_list) % 3 != 0:
+ raise InternalError("Lex dictionary has invalid format (input not divisible by 3): %s" % as_list)
+
+ result = {}
+ for key, sep, value in [as_list[3*i:3*i+3] for i in range(0, len(as_list)//3)]:
+ if sep != '->':
+ raise InternalError("Lex dictionary has invalid format")
+ result[key] = value
+ return result
+
+
+def lex_me_harder(infofile, allowed_groups, allowed_maps, name_val_pairs):
+ """
+ Generic lexer function for info.txt and src/build-data files
+ """
+ out = LexResult()
+
+ # Format as a nameable Python variable
+ def py_var(group):
+ return group.replace(':', '_')
lexer = shlex.shlex(open(infofile), infofile, posix=True)
- lexer.wordchars += '|:.<>/,-!+' # handle various funky chars in info.txt
+ lexer.wordchars += ':.<>/,-!?+*' # handle various funky chars in info.txt
- for group in allowed_groups:
- to_obj.__dict__[py_var(group)] = []
- for (key,val) in name_val_pairs.items():
- to_obj.__dict__[key] = val
+ groups = allowed_groups + allowed_maps
+ for group in groups:
+ out.__dict__[py_var(group)] = []
+ for (key, val) in name_val_pairs.items():
+ out.__dict__[key] = val
- def lexed_tokens(): # Convert to an interator
- token = lexer.get_token()
- while token != None:
- yield token
+ def lexed_tokens(): # Convert to an iterator
+ while True:
token = lexer.get_token()
+ if token != lexer.eof:
+ yield token
+ else:
+ return
for token in lexed_tokens():
match = re.match('<(.*)>', token)
@@ -503,106 +701,175 @@ def lex_me_harder(infofile, to_obj, allowed_groups, name_val_pairs):
if match is not None:
group = match.group(1)
- if group not in allowed_groups:
+ if group not in groups:
raise LexerError('Unknown group "%s"' % (group),
- lexer.lineno)
+ infofile, lexer.lineno)
end_marker = '</' + group + '>'
token = lexer.get_token()
while token != end_marker:
- to_obj.__dict__[py_var(group)].append(token)
+ out.__dict__[py_var(group)].append(token)
token = lexer.get_token()
if token is None:
raise LexerError('Group "%s" not terminated' % (group),
- lexer.lineno)
+ infofile, lexer.lineno)
elif token in name_val_pairs.keys():
- next_val = lexer.get_token()
-
- if type(to_obj.__dict__[token]) is list:
- to_obj.__dict__[token].append(next_val)
+ if isinstance(out.__dict__[token], list):
+ out.__dict__[token].append(lexer.get_token())
else:
- to_obj.__dict__[token] = next_val
+ out.__dict__[token] = lexer.get_token()
else: # No match -> error
- raise LexerError('Bad token "%s"' % (token), lexer.lineno)
+ raise LexerError('Bad token "%s"' % (token), infofile, lexer.lineno)
-"""
-Convert a lex'ed map (from build-data files) from a list to a dict
-"""
-def force_to_dict(l):
- return dict(zip(l[::3],l[2::3]))
+ for group in allowed_maps:
+ out.__dict__[group] = parse_lex_dict(out.__dict__[group])
-"""
-Represents the information about a particular module
-"""
-class ModuleInfo(object):
+ return out
+class InfoObject(object):
def __init__(self, infofile):
+ """
+ Constructor sets members `infofile`, `lives_in`, `parent_module` and `basename`
+ """
+
+ self.infofile = infofile
+ (dirname, basename) = os.path.split(infofile)
+ self.lives_in = dirname
+ if basename == 'info.txt':
+ (obj_dir, self.basename) = os.path.split(dirname)
+ if os.access(os.path.join(obj_dir, 'info.txt'), os.R_OK):
+ self.parent_module = os.path.basename(obj_dir)
+ else:
+ self.parent_module = None
+ else:
+ self.basename = basename.replace('.txt', '')
- lex_me_harder(infofile, self,
- ['source', 'header:internal', 'header:public',
- 'requires', 'os', 'arch', 'cc', 'libs',
- 'comment'],
- {
- 'load_on': 'auto',
- 'define': [],
- 'uses_tr1': 'false',
- 'need_isa': None,
- 'mp_bits': 0 })
-
- def extract_files_matching(basedir, suffixes):
- for (dirpath, dirnames, filenames) in os.walk(basedir):
- if dirpath == basedir:
- for filename in filenames:
- if filename.startswith('.'):
- continue
-
- for suffix in suffixes:
- if filename.endswith(suffix):
- yield filename
- if self.source == []:
- self.source = list(extract_files_matching(self.lives_in, ['.cpp', '.S']))
+class ModuleInfo(InfoObject):
+ """
+ Represents the information about a particular module
+ """
- if self.header_internal == [] and self.header_public == []:
- self.header_public = list(extract_files_matching(self.lives_in, ['.h']))
+ def __init__(self, infofile):
+ super(ModuleInfo, self).__init__(infofile)
+ lex = lex_me_harder(
+ infofile,
+ ['header:internal', 'header:public', 'header:external', 'requires',
+ 'os_features', 'arch', 'cc', 'libs', 'frameworks', 'comment', 'warning'
+ ],
+ ['defines'],
+ {
+ 'load_on': 'auto',
+ 'need_isa': ''
+ })
+
+ def check_header_duplicates(header_list_public, header_list_internal):
+ pub_header = set(header_list_public)
+ int_header = set(header_list_internal)
+ if not pub_header.isdisjoint(int_header):
+ logging.error("Module %s header contains same header in public and internal sections" % self.infofile)
+
+ check_header_duplicates(lex.header_public, lex.header_internal)
+
+ all_source_files = []
+ all_header_files = []
+
+ for fspath in os.listdir(self.lives_in):
+ if fspath.endswith('.cpp'):
+ all_source_files.append(fspath)
+ elif fspath.endswith('.h'):
+ all_header_files.append(fspath)
+
+ self.source = all_source_files
+
+ # If not entry for the headers, all are assumed public
+ if lex.header_internal == [] and lex.header_public == []:
+ self.header_public = list(all_header_files)
+ self.header_internal = []
+ else:
+ self.header_public = lex.header_public
+ self.header_internal = lex.header_internal
+ self.header_external = lex.header_external
# Coerce to more useful types
def convert_lib_list(l):
+ if len(l) % 3 != 0:
+ raise InternalError("Bad <libs> in module %s" % (self.basename))
result = {}
+
+ for sep in l[1::3]:
+ if sep != '->':
+ raise InternalError("Bad <libs> in module %s" % (self.basename))
+
for (targetlist, vallist) in zip(l[::3], l[2::3]):
vals = vallist.split(',')
for target in targetlist.split(','):
result[target] = result.setdefault(target, []) + vals
return result
- self.libs = convert_lib_list(self.libs)
-
- def add_dir_name(filename):
- if filename.count(':') == 0:
- return os.path.join(self.lives_in, filename)
-
- # modules can request to add files of the form
- # MODULE_NAME:FILE_NAME to add a file from another module
- # For these, assume other module is always in a
- # neighboring directory; this is true for all current uses
- return os.path.join(os.path.split(self.lives_in)[0],
- *filename.split(':'))
-
- self.source = [add_dir_name(s) for s in self.source]
- self.header_internal = [add_dir_name(s) for s in self.header_internal]
- self.header_public = [add_dir_name(s) for s in self.header_public]
-
- self.mp_bits = int(self.mp_bits)
-
- self.uses_tr1 = (True if self.uses_tr1 == 'yes' else False)
-
- if self.comment != []:
- self.comment = ' '.join(self.comment)
- else:
- self.comment = None
+ # Convert remaining lex result to members
+ self.arch = lex.arch
+ self.cc = lex.cc
+ self.comment = ' '.join(lex.comment) if lex.comment else None
+ self._defines = lex.defines
+ self._validate_defines_content(self._defines)
+ self.frameworks = convert_lib_list(lex.frameworks)
+ self.libs = convert_lib_list(lex.libs)
+ self.load_on = lex.load_on
+ self.need_isa = lex.need_isa.split(',') if lex.need_isa else []
+ self.os_features = lex.os_features
+ self.requires = lex.requires
+ self.warning = ' '.join(lex.warning) if lex.warning else None
+
+ # Modify members
+ self.source = [normalize_source_path(os.path.join(self.lives_in, s)) for s in self.source]
+ self.header_internal = [os.path.join(self.lives_in, s) for s in self.header_internal]
+ self.header_public = [os.path.join(self.lives_in, s) for s in self.header_public]
+ self.header_external = [os.path.join(self.lives_in, s) for s in self.header_external]
+
+ # Filesystem read access check
+ for src in self.source + self.header_internal + self.header_public + self.header_external:
+ if not os.access(src, os.R_OK):
+ logging.error("Missing file %s in %s" % (src, infofile))
+
+ # Check for duplicates
+ def intersect_check(type_a, list_a, type_b, list_b):
+ intersection = set.intersection(set(list_a), set(list_b))
+ if intersection:
+ logging.error('Headers %s marked both %s and %s' % (' '.join(intersection), type_a, type_b))
+
+ intersect_check('public', self.header_public, 'internal', self.header_internal)
+ intersect_check('public', self.header_public, 'external', self.header_external)
+ intersect_check('external', self.header_external, 'internal', self.header_internal)
+
+ @staticmethod
+ def _validate_defines_content(defines):
+ for key, value in defines.items():
+ if not re.match('^[0-9A-Za-z_]{3,30}$', key):
+ raise InternalError('Module defines key has invalid format: "%s"' % key)
+ if not re.match('^[0-9]{8}$', value):
+ raise InternalError('Module defines value has invalid format: "%s"' % value)
+
+ def cross_check(self, arch_info, cc_info, all_os_features):
+
+ for feat in set(flatten([o.split(',') for o in self.os_features])):
+ if feat not in all_os_features:
+ logging.error("Module %s uses an OS feature (%s) which no OS supports", self.infofile, feat)
+
+ for supp_cc in self.cc:
+ if supp_cc not in cc_info:
+ colon_idx = supp_cc.find(':')
+ # a versioned compiler dependency
+ if colon_idx > 0 and supp_cc[0:colon_idx] in cc_info:
+ pass
+ else:
+ raise InternalError('Module %s mentions unknown compiler %s' % (self.infofile, supp_cc))
+ for supp_arch in self.arch:
+ if supp_arch not in arch_info:
+ raise InternalError('Module %s mentions unknown arch %s' % (self.infofile, supp_arch))
def sources(self):
return self.source
@@ -613,321 +880,556 @@ class ModuleInfo(object):
def internal_headers(self):
return self.header_internal
+ def external_headers(self):
+ return self.header_external
+
def defines(self):
- return ['HAS_' + d for d in self.define]
+ return [(key + ' ' + value) for key, value in self._defines.items()]
def compatible_cpu(self, archinfo, options):
-
arch_name = archinfo.basename
cpu_name = options.cpu
+ for isa in self.need_isa:
+ if isa in options.disable_intrinsics:
+ return False # explicitly disabled
+
+ if isa not in archinfo.isa_extensions:
+ return False
+
if self.arch != []:
if arch_name not in self.arch and cpu_name not in self.arch:
return False
- if self.need_isa != None:
- if self.need_isa in options.disable_isa_extns:
- return False # explicitly disabled
+ return True
- if self.need_isa in options.enable_isa_extns:
- return True # explicitly enabled
+ def compatible_os(self, os_data, options):
+ if not self.os_features:
+ return True
- # Default to whatever the CPU is supposed to support
- return self.need_isa in archinfo.isa_extensions_in(cpu_name)
+ def has_all(needed, provided):
+ for n in needed:
+ if n not in provided:
+ return False
+ return True
- return True
+ provided_features = os_data.enabled_features(options)
- def compatible_os(self, os):
- return self.os == [] or os in self.os
+ for feature_set in self.os_features:
+ if has_all(feature_set.split(','), provided_features):
+ return True
- def compatible_compiler(self, cc):
- return self.cc == [] or cc in self.cc
+ return False
- def tr1_ok(self, with_tr1):
- if self.uses_tr1:
- return with_tr1 in ['boost', 'system']
- else:
+ def compatible_compiler(self, ccinfo, cc_min_version, arch):
+ # Check if this compiler supports the flags we need
+ def supported_isa_flags(ccinfo, arch):
+ for isa in self.need_isa:
+ if ccinfo.isa_flags_for(isa, arch) is None:
+ return False
return True
- def dependencies(self):
- # utils is an implicit dep (contains types, etc)
- deps = self.requires + ['utils']
+ # Check if module gives explicit compiler dependencies
+ def supported_compiler(ccinfo, cc_min_version):
+ if self.cc == []:
+ # no compiler restriction
+ return True
+
+ if ccinfo.basename in self.cc:
+ # compiler is supported, independent of version
+ return True
+
+ # Maybe a versioned compiler dep
+ for cc in self.cc:
+ try:
+ name, version = cc.split(":")
+ if name == ccinfo.basename:
+ min_cc_version = [int(v) for v in version.split('.')]
+ cur_cc_version = [int(v) for v in cc_min_version.split('.')]
+ # With lists of ints, this does what we want
+ return cur_cc_version >= min_cc_version
+ except ValueError:
+ # No version part specified
+ pass
+
+ return False # compiler not listed
+
+ return supported_isa_flags(ccinfo, arch) and supported_compiler(ccinfo, cc_min_version)
+
+ def dependencies(self, osinfo):
+ # base is an implicit dep for all submodules
+ deps = ['base']
if self.parent_module != None:
deps.append(self.parent_module)
+
+ for req in self.requires:
+ if req.find('?') != -1:
+ (cond, dep) = req.split('?')
+ if osinfo is None or cond in osinfo.target_features:
+ deps.append(dep)
+ else:
+ deps.append(req)
+
return deps
- """
- Ensure that all dependencies of this module actually exist, warning
- about any that do not
- """
def dependencies_exist(self, modules):
- all_deps = [s.split('|') for s in self.dependencies()]
+ """
+ Ensure that all dependencies of this module actually exist, warning
+ about any that do not
+ """
- for missing in [s for s in flatten(all_deps) if s not in modules]:
- logging.warn("Module '%s', dep of '%s', does not exist" % (
+ missing = [s for s in self.dependencies(None) if s not in modules]
+
+ if missing:
+ logging.error("Module '%s', dep of '%s', does not exist" % (
missing, self.basename))
- def __cmp__(self, other):
- if self.basename < other.basename:
- return -1
- if self.basename == other.basename:
- return 0
- return 1
-class ArchInfo(object):
+class ModulePolicyInfo(InfoObject):
+ def __init__(self, infofile):
+ super(ModulePolicyInfo, self).__init__(infofile)
+ lex = lex_me_harder(
+ infofile,
+ ['required', 'if_available', 'prohibited'],
+ [],
+ {})
+
+ self.if_available = lex.if_available
+ self.required = lex.required
+ self.prohibited = lex.prohibited
+
+ def cross_check(self, modules):
+ def check(tp, lst):
+ for mod in lst:
+ if mod not in modules:
+ logging.error("Module policy %s includes non-existent module %s in <%s>" % (
+ self.infofile, mod, tp))
+
+ check('required', self.required)
+ check('if_available', self.if_available)
+ check('prohibited', self.prohibited)
+
+
+class ArchInfo(InfoObject):
def __init__(self, infofile):
- lex_me_harder(infofile, self,
- ['aliases', 'submodels', 'submodel_aliases', 'isa_extn'],
- { 'endian': None,
- 'family': None,
- 'unaligned': 'no'
- })
+ super(ArchInfo, self).__init__(infofile)
+ lex = lex_me_harder(
+ infofile,
+ ['aliases', 'isa_extensions'],
+ [],
+ {
+ 'endian': None,
+ 'family': None,
+ 'wordsize': 32
+ })
+
+ self.aliases = lex.aliases
+ self.endian = lex.endian
+ self.family = lex.family
+ self.isa_extensions = lex.isa_extensions
+ self.wordsize = int(lex.wordsize)
+
+ if self.wordsize not in [32, 64]:
+ logging.error('Unexpected wordsize %d for arch %s', self.wordsize, infofile)
+
+ alphanumeric = re.compile('^[a-z0-9]+$')
+ for isa in self.isa_extensions:
+ if alphanumeric.match(isa) is None:
+ logging.error('Invalid name for ISA extension "%s"', isa)
+
+ def supported_isa_extensions(self, cc, options):
+ isas = []
+
+ for isa in self.isa_extensions:
+ if isa not in options.disable_intrinsics:
+ if cc.isa_flags_for(isa, self.basename) is not None:
+ isas.append(isa)
+
+ return sorted(isas)
+
+
+class CompilerInfo(InfoObject): # pylint: disable=too-many-instance-attributes
+ def __init__(self, infofile):
+ super(CompilerInfo, self).__init__(infofile)
+ lex = lex_me_harder(
+ infofile,
+ [],
+ ['cpu_flags', 'cpu_flags_no_debug', 'so_link_commands', 'binary_link_commands',
+ 'mach_abi_linking', 'isa_flags', 'sanitizers'],
+ {
+ 'binary_name': None,
+ 'linker_name': None,
+ 'macro_name': None,
+ 'output_to_object': '-o ',
+ 'output_to_exe': '-o ',
+ 'add_include_dir_option': '-I',
+ 'add_lib_dir_option': '-L',
+ 'add_sysroot_option': '',
+ 'add_lib_option': '-l',
+ 'add_framework_option': '-framework ',
+ 'preproc_flags': '-E',
+ 'compile_flags': '-c',
+ 'debug_info_flags': '-g',
+ 'optimization_flags': '',
+ 'size_optimization_flags': '',
+ 'sanitizer_optimization_flags': '',
+ 'coverage_flags': '',
+ 'stack_protector_flags': '',
+ 'shared_flags': '',
+ 'lang_flags': '',
+ 'warning_flags': '',
+ 'maintainer_warning_flags': '',
+ 'visibility_build_flags': '',
+ 'visibility_attribute': '',
+ 'ar_command': '',
+ 'ar_options': '',
+ 'ar_output_to': '',
+ })
+
+ self.add_framework_option = lex.add_framework_option
+ self.add_include_dir_option = lex.add_include_dir_option
+ self.add_lib_dir_option = lex.add_lib_dir_option
+ self.add_lib_option = lex.add_lib_option
+ self.add_sysroot_option = lex.add_sysroot_option
+ self.ar_command = lex.ar_command
+ self.ar_options = lex.ar_options
+ self.ar_output_to = lex.ar_output_to
+ self.binary_link_commands = lex.binary_link_commands
+ self.binary_name = lex.binary_name
+ self.cpu_flags = lex.cpu_flags
+ self.cpu_flags_no_debug = lex.cpu_flags_no_debug
+ self.compile_flags = lex.compile_flags
+ self.coverage_flags = lex.coverage_flags
+ self.debug_info_flags = lex.debug_info_flags
+ self.isa_flags = lex.isa_flags
+ self.lang_flags = lex.lang_flags
+ self.linker_name = lex.linker_name
+ self.mach_abi_linking = lex.mach_abi_linking
+ self.macro_name = lex.macro_name
+ self.maintainer_warning_flags = lex.maintainer_warning_flags
+ self.optimization_flags = lex.optimization_flags
+ self.output_to_exe = lex.output_to_exe
+ self.output_to_object = lex.output_to_object
+ self.preproc_flags = lex.preproc_flags
+ self.sanitizers = lex.sanitizers
+ self.sanitizer_types = []
+ self.sanitizer_optimization_flags = lex.sanitizer_optimization_flags
+ self.shared_flags = lex.shared_flags
+ self.size_optimization_flags = lex.size_optimization_flags
+ self.so_link_commands = lex.so_link_commands
+ self.stack_protector_flags = lex.stack_protector_flags
+ self.visibility_attribute = lex.visibility_attribute
+ self.visibility_build_flags = lex.visibility_build_flags
+ self.warning_flags = lex.warning_flags
+
+ def isa_flags_for(self, isa, arch):
+ if isa in self.isa_flags:
+ return self.isa_flags[isa]
+ arch_isa = '%s:%s' % (arch, isa)
+ if arch_isa in self.isa_flags:
+ return self.isa_flags[arch_isa]
+ return None
+
+ def get_isa_specific_flags(self, isas, arch, options):
+ flags = set()
+
+ def simd32_impl():
+ for simd_isa in ['sse2', 'altivec', 'neon']:
+ if simd_isa in arch.isa_extensions and \
+ simd_isa not in options.disable_intrinsics and \
+ self.isa_flags_for(simd_isa, arch.basename):
+ return simd_isa
+ return None
+
+ for isa in isas:
+
+ if isa == 'simd':
+ isa = simd32_impl()
+
+ if isa is None:
+ continue
+
+ flagset = self.isa_flags_for(isa, arch.basename)
+ if flagset is None:
+ raise UserError('Compiler %s does not support %s' % (self.basename, isa))
+ flags.add(flagset)
+
+ return " ".join(sorted(flags))
- def convert_isa_list(input):
- isa_info = {}
- for line in self.isa_extn:
- (isa,cpus) = line.split(':')
- for cpu in cpus.split(','):
- isa_info.setdefault(cpu, []).append(isa)
- return isa_info
+ def gen_shared_flags(self, options):
+ """
+ Return the shared library build flags, if any
+ """
- self.isa_extn = convert_isa_list(self.isa_extn)
+ def flag_builder():
+ if options.build_shared_lib:
+ yield self.shared_flags
+ yield self.visibility_build_flags
- self.submodel_aliases = force_to_dict(self.submodel_aliases)
+ return ' '.join(list(flag_builder()))
- self.unaligned_ok = (1 if self.unaligned == 'ok' else 0)
+ def gen_visibility_attribute(self, options):
+ if options.build_shared_lib:
+ return self.visibility_attribute
+ return ''
- """
- Return ISA extensions specific to this CPU
- """
- def isa_extensions_in(self, cpu_type):
- return sorted(self.isa_extn.get(cpu_type, []) +
- self.isa_extn.get('all', []))
+ def mach_abi_link_flags(self, options, with_debug_info=None):
+ #pylint: disable=too-many-branches
- """
- Return a list of all submodels for this arch, ordered longest
- to shortest
- """
- def all_submodels(self):
- return sorted([(k,k) for k in self.submodels] +
- [k for k in self.submodel_aliases.items()],
- key = lambda k: len(k[0]), reverse = True)
+ """
+ Return the machine specific ABI flags
+ """
- """
- Return CPU-specific defines for build.h
- """
- def defines(self, options):
- def form_macro(cpu_name):
- return cpu_name.upper().replace('.', '').replace('-', '_')
+ if with_debug_info is None:
+ with_debug_info = options.with_debug_info
- macros = ['TARGET_ARCH_IS_%s' %
- (form_macro(self.basename.upper()))]
+ def mach_abi_groups():
- if self.basename != options.cpu:
- macros.append('TARGET_CPU_IS_%s' % (form_macro(options.cpu)))
+ yield 'all'
- enabled_isas = set(self.isa_extensions_in(options.cpu) +
- options.enable_isa_extns)
- disabled_isas = set(options.disable_isa_extns)
+ if options.msvc_runtime is None:
+ if with_debug_info:
+ yield 'rt-debug'
+ else:
+ yield 'rt'
- isa_extensions = sorted(enabled_isas - disabled_isas)
+ for all_except in [s for s in self.mach_abi_linking.keys() if s.startswith('all!')]:
+ exceptions = all_except[4:].split(',')
+ if options.os not in exceptions and options.arch not in exceptions:
+ yield all_except
- for isa in isa_extensions:
- macros.append('TARGET_CPU_HAS_%s' % (form_macro(isa)))
+ yield options.os
+ yield options.cpu
- endian = options.with_endian or self.endian
+ abi_link = set()
+ for what in mach_abi_groups():
+ if what in self.mach_abi_linking:
+ flag = self.mach_abi_linking.get(what)
+ if flag != None and flag != '' and flag not in abi_link:
+ abi_link.add(flag)
- if endian != None:
- macros.append('TARGET_CPU_IS_%s_ENDIAN' % (endian.upper()))
- logging.info('Assuming CPU is %s endian' % (endian))
+ if options.msvc_runtime:
+ abi_link.add("/" + options.msvc_runtime)
- unaligned_ok = options.unaligned_mem
- if unaligned_ok is None:
- unaligned_ok = self.unaligned_ok
- if unaligned_ok:
- logging.info('Assuming unaligned memory access works')
+ if options.with_stack_protector and self.stack_protector_flags != '':
+ abi_link.add(self.stack_protector_flags)
- if self.family is not None:
- macros.append('TARGET_CPU_IS_%s_FAMILY' % (self.family.upper()))
+ if options.with_coverage_info:
+ if self.coverage_flags == '':
+ raise UserError('No coverage handling for %s' % (self.basename))
+ abi_link.add(self.coverage_flags)
- macros.append('TARGET_UNALIGNED_MEMORY_ACCESS_OK %d' % (unaligned_ok))
+ if options.with_sanitizers or options.enable_sanitizers != '':
+ if not self.sanitizers:
+ raise UserError('No sanitizer handling for %s' % (self.basename))
- return macros
+ default_san = self.sanitizers['default'].split(',')
-class CompilerInfo(object):
- def __init__(self, infofile):
- lex_me_harder(infofile, self,
- ['so_link_flags', 'mach_opt', 'mach_abi_linking'],
- { 'binary_name': None,
- 'macro_name': None,
- 'compile_option': '-c ',
- 'output_to_option': '-o ',
- 'add_include_dir_option': '-I',
- 'add_lib_dir_option': '-L',
- 'add_lib_option': '-l',
- 'lib_opt_flags': '',
- 'check_opt_flags': '',
- 'debug_flags': '',
- 'no_debug_flags': '',
- 'shared_flags': '',
- 'lang_flags': '',
- 'warning_flags': '',
- 'maintainer_warning_flags': '',
- 'visibility_build_flags': '',
- 'visibility_attribute': '',
- 'ar_command': None,
- 'makefile_style': '',
- 'has_tr1': False,
- })
-
- self.so_link_flags = force_to_dict(self.so_link_flags)
- self.mach_abi_linking = force_to_dict(self.mach_abi_linking)
-
- self.mach_opt_flags = {}
-
- while self.mach_opt != []:
- proc = self.mach_opt.pop(0)
- if self.mach_opt.pop(0) != '->':
- raise Exception('Parsing err in %s mach_opt' % (self.basename))
-
- flags = self.mach_opt.pop(0)
- regex = ''
-
- if len(self.mach_opt) > 0 and \
- (len(self.mach_opt) == 1 or self.mach_opt[1] != '->'):
- regex = self.mach_opt.pop(0)
-
- self.mach_opt_flags[proc] = (flags,regex)
-
- del self.mach_opt
+ if options.enable_sanitizers:
+ san = options.enable_sanitizers.split(',')
+ else:
+ san = default_san
- """
- Return the shared library build flags, if any
- """
- def gen_shared_flags(self, options):
- def flag_builder():
- if options.build_shared_lib:
- yield self.shared_flags
- if options.with_visibility:
- yield self.visibility_build_flags
+ for s in san:
+ if s not in self.sanitizers:
+ raise UserError('No flags defined for sanitizer %s in %s' % (s, self.basename))
- return ' '.join(list(flag_builder()))
+ if s == 'default':
+ abi_link.update([self.sanitizers[s] for s in default_san])
+ else:
+ abi_link.add(self.sanitizers[s])
- def gen_visibility_attribute(self, options):
- if options.build_shared_lib and options.with_visibility:
- return self.visibility_attribute
- return ''
+ self.sanitizer_types = san
- """
- Return the machine specific ABI flags
- """
- def mach_abi_link_flags(self, osname, arch, submodel, debug_p):
+ if options.with_openmp:
+ if 'openmp' not in self.mach_abi_linking:
+ raise UserError('No support for OpenMP for %s' % (self.basename))
+ abi_link.add(self.mach_abi_linking['openmp'])
- def all():
- if debug_p:
- return 'all-debug'
- return 'all'
+ abi_flags = ' '.join(sorted(abi_link))
- abi_link = set()
- for what in [all(), osname, arch, submodel]:
- if self.mach_abi_linking.get(what) != None:
- abi_link.add(self.mach_abi_linking.get(what))
+ if options.cc_abi_flags != '':
+ abi_flags += ' ' + options.cc_abi_flags
- if len(abi_link) == 0:
- return ''
- return ' ' + ' '.join(abi_link)
+ return abi_flags
- """
- Return the flags for MACH_OPT
- """
- def mach_opts(self, arch, submodel):
+ def cc_warning_flags(self, options):
+ def gen_flags():
+ yield self.warning_flags
+ if options.maintainer_mode:
+ yield self.maintainer_warning_flags
- def submodel_fixup(tup):
- return tup[0].replace('SUBMODEL', submodel.replace(tup[1], ''))
+ return (' '.join(gen_flags())).strip()
- if submodel == arch:
- return ''
+ def cc_lang_flags(self):
+ return self.lang_flags
- if submodel in self.mach_opt_flags:
- return submodel_fixup(self.mach_opt_flags[submodel])
- if arch in self.mach_opt_flags:
- return submodel_fixup(self.mach_opt_flags[arch])
+ def cc_compile_flags(self, options, with_debug_info=None, enable_optimizations=None):
+ def gen_flags(with_debug_info, enable_optimizations):
- return ''
+ sanitizers_enabled = options.with_sanitizers or (len(options.enable_sanitizers) > 0)
- """
- Return the flags for LIB_OPT
- """
- def library_opt_flags(self, options):
- def gen_flags():
- if options.debug_build:
- yield self.debug_flags
+ if with_debug_info is None:
+ with_debug_info = options.with_debug_info
+ if enable_optimizations is None:
+ enable_optimizations = not options.no_optimizations
- if not options.no_optimizations:
- yield self.lib_opt_flags
+ if with_debug_info:
+ yield self.debug_info_flags
- if not options.debug_build:
- yield self.no_debug_flags
+ if enable_optimizations:
+ if options.optimize_for_size:
+ if self.size_optimization_flags != '':
+ yield self.size_optimization_flags
+ else:
+ logging.warning("No size optimization flags set for current compiler")
+ yield self.optimization_flags
+ elif sanitizers_enabled and self.sanitizer_optimization_flags != '':
+ yield self.sanitizer_optimization_flags
+ else:
+ yield self.optimization_flags
- return (' '.join(gen_flags())).strip()
+ if options.arch in self.cpu_flags:
+ yield self.cpu_flags[options.arch]
- """
- Return the command needed to link a shared object
- """
- def so_link_command_for(self, osname):
- if osname in self.so_link_flags:
- return self.so_link_flags[osname]
- if 'default' in self.so_link_flags:
- return self.so_link_flags['default']
- return ''
+ if options.arch in self.cpu_flags_no_debug:
- """
- Return defines for build.h
- """
- def defines(self, with_tr1):
-
- def tr1_macro():
- if with_tr1:
- if with_tr1 == 'boost':
- return ['USE_BOOST_TR1']
- elif with_tr1 == 'system':
- return ['USE_STD_TR1']
- elif self.has_tr1:
- return ['USE_STD_TR1']
- return []
+ # Only enable these if no debug/sanitizer options enabled
+
+ if not (options.debug_mode or sanitizers_enabled):
+ yield self.cpu_flags_no_debug[options.arch]
+
+ return (' '.join(gen_flags(with_debug_info, enable_optimizations))).strip()
+
+ @staticmethod
+ def _so_link_search(osname, debug_info):
+ so_link_typ = [osname, 'default']
+ if debug_info:
+ so_link_typ = [l + '-debug' for l in so_link_typ] + so_link_typ
+ return so_link_typ
- return ['BUILD_COMPILER_IS_' + self.macro_name] + tr1_macro()
+ def so_link_command_for(self, osname, options):
+ """
+ Return the command needed to link a shared object
+ """
-class OsInfo(object):
+ for s in self._so_link_search(osname, options.with_debug_info):
+ if s in self.so_link_commands:
+ return self.so_link_commands[s]
+
+ raise InternalError(
+ "No shared library link command found for target '%s' in compiler settings '%s'" %
+ (osname, self.infofile))
+
+ def binary_link_command_for(self, osname, options):
+ """
+ Return the command needed to link an app/test object
+ """
+
+ for s in self._so_link_search(osname, options.with_debug_info):
+ if s in self.binary_link_commands:
+ return self.binary_link_commands[s]
+
+ return '$(LINKER)'
+
+class OsInfo(InfoObject): # pylint: disable=too-many-instance-attributes
def __init__(self, infofile):
- lex_me_harder(infofile, self,
- ['aliases', 'target_features'],
- { 'os_type': None,
- 'obj_suffix': 'o',
- 'so_suffix': 'so',
- 'static_suffix': 'a',
- 'ar_command': 'ar crs',
- 'ar_needs_ranlib': False,
- 'install_root': '/usr/local',
- 'header_dir': 'include',
- 'lib_dir': 'lib',
- 'doc_dir': 'share/doc',
- 'build_shared': 'yes',
- 'install_cmd_data': 'install -m 644',
- 'install_cmd_exec': 'install -m 755'
- })
-
- self.ar_needs_ranlib = bool(self.ar_needs_ranlib)
-
- self.build_shared = (True if self.build_shared == 'yes' else False)
-
- def ranlib_command(self):
- return ('ranlib' if self.ar_needs_ranlib else 'true')
+ super(OsInfo, self).__init__(infofile)
+ lex = lex_me_harder(
+ infofile,
+ ['aliases', 'target_features'],
+ [],
+ {
+ 'program_suffix': '',
+ 'obj_suffix': 'o',
+ 'soname_suffix': '',
+ 'soname_pattern_patch': '',
+ 'soname_pattern_abi': '',
+ 'soname_pattern_base': '',
+ 'static_suffix': 'a',
+ 'ar_command': 'ar',
+ 'ar_options': '',
+ 'ar_output_to': '',
+ 'install_root': '/usr/local',
+ 'header_dir': 'include',
+ 'bin_dir': 'bin',
+ 'lib_dir': 'lib',
+ 'doc_dir': 'share/doc',
+ 'man_dir': 'share/man',
+ 'use_stack_protector': 'true',
+ 'so_post_link_command': '',
+ 'cli_exe_name': 'botan',
+ 'lib_prefix': 'lib',
+ 'library_name': 'botan{suffix}-{major}',
+ })
+
+ if lex.ar_command == 'ar' and lex.ar_options == '':
+ lex.ar_options = 'crs'
+
+ if lex.soname_pattern_base:
+ self.soname_pattern_base = lex.soname_pattern_base
+ if lex.soname_pattern_patch == '' and lex.soname_pattern_abi == '':
+ self.soname_pattern_patch = lex.soname_pattern_base
+ self.soname_pattern_abi = lex.soname_pattern_base
+ elif lex.soname_pattern_abi != '' and lex.soname_pattern_abi != '':
+ self.soname_pattern_patch = lex.soname_pattern_patch
+ self.soname_pattern_abi = lex.soname_pattern_abi
+ else:
+ # base set, only one of patch/abi set
+ raise InternalError("Invalid soname_patterns in %s" % (self.infofile))
+ else:
+ if lex.soname_suffix:
+ self.soname_pattern_base = "libbotan{lib_suffix}-{version_major}.%s" % (lex.soname_suffix)
+ self.soname_pattern_abi = self.soname_pattern_base + ".{abi_rev}"
+ self.soname_pattern_patch = self.soname_pattern_abi + ".{version_minor}.{version_patch}"
+ else:
+ # Could not calculate soname_pattern_*
+ # This happens for OSs without shared library support (e.g. nacl, mingw, includeos, cygwin)
+ self.soname_pattern_base = None
+ self.soname_pattern_abi = None
+ self.soname_pattern_patch = None
+
+ self._aliases = lex.aliases
+ self.ar_command = lex.ar_command
+ self.ar_options = lex.ar_options
+ self.bin_dir = lex.bin_dir
+ self.cli_exe_name = lex.cli_exe_name
+ self.doc_dir = lex.doc_dir
+ self.header_dir = lex.header_dir
+ self.install_root = lex.install_root
+ self.lib_dir = lex.lib_dir
+ self.lib_prefix = lex.lib_prefix
+ self.library_name = lex.library_name
+ self.man_dir = lex.man_dir
+ self.obj_suffix = lex.obj_suffix
+ self.program_suffix = lex.program_suffix
+ self.so_post_link_command = lex.so_post_link_command
+ self.static_suffix = lex.static_suffix
+ self.target_features = lex.target_features
+ self.use_stack_protector = (lex.use_stack_protector == "true")
+
+ def matches_name(self, nm):
+ if nm in self._aliases:
+ return True
- def defines(self):
- return ['TARGET_OS_IS_%s' % (self.basename.upper())] + \
- ['TARGET_OS_HAS_' + feat.upper()
- for feat in sorted(self.target_features)]
+ for alias in self._aliases:
+ if re.match(alias, nm):
+ return True
+ return False
+
+ def building_shared_supported(self):
+ return self.soname_pattern_base != None
+
+ def enabled_features(self, options):
+ feats = []
+ for feat in self.target_features:
+ if feat not in options.without_os_features:
+ feats.append(feat)
+ for feat in options.with_os_features:
+ if feat not in self.target_features:
+ feats.append(feat)
+
+ return sorted(feats)
def fixup_proc_name(proc):
proc = proc.lower().replace(' ', '')
@@ -941,711 +1443,1152 @@ def canon_processor(archinfo, proc):
# First, try to search for an exact match
for ainfo in archinfo.values():
if ainfo.basename == proc or proc in ainfo.aliases:
- return (ainfo.basename, ainfo.basename)
+ return ainfo.basename
- for (match,submodel) in ainfo.all_submodels():
- if proc == submodel or proc == match:
- return (ainfo.basename, submodel)
+ return None
- logging.debug('Could not find an exact match for CPU "%s"' % (proc))
+def system_cpu_info():
- # Now, try searching via regex match
- for ainfo in archinfo.values():
- for (match,submodel) in ainfo.all_submodels():
- if re.search(match, proc) != None:
- logging.debug('Possible match "%s" with "%s" (%s)' % (
- proc, match, submodel))
- return (ainfo.basename, submodel)
+ cpu_info = []
- logging.debug('Known CPU names: ' + ' '.join(
- sorted(flatten([[ainfo.basename] + \
- ainfo.aliases + \
- [x for (x,_) in ainfo.all_submodels()]
- for ainfo in archinfo.values()]))))
+ if platform.machine() != '':
+ cpu_info.append(platform.machine())
- raise Exception('Unknown or unidentifiable processor "%s"' % (proc))
+ if platform.processor() != '':
+ cpu_info.append(platform.processor())
-def guess_processor(archinfo):
- base_proc = platform.machine()
+ if 'uname' in os.__dict__:
+ cpu_info.append(os.uname()[4])
- if base_proc == '':
- raise Exception('Could not determine target CPU; set with --cpu')
+ return cpu_info
- full_proc = fixup_proc_name(platform.processor()) or base_proc
-
- for ainfo in archinfo.values():
- if ainfo.basename == base_proc or base_proc in ainfo.aliases:
- for (match,submodel) in ainfo.all_submodels():
- if re.search(match, full_proc) != None:
- return (ainfo.basename, submodel)
+def guess_processor(archinfo):
+ for info_part in system_cpu_info():
+ if info_part:
+ match = canon_processor(archinfo, info_part)
+ if match != None:
+ logging.debug("Matched '%s' to processor '%s'" % (info_part, match))
+ return match, info_part
+ else:
+ logging.debug("Failed to deduce CPU from '%s'" % info_part)
- return canon_processor(archinfo, ainfo.basename)
+ raise UserError('Could not determine target CPU; set with --cpu')
- # No matches, so just use the base proc type
- return canon_processor(archinfo, base_proc)
-"""
-Read a whole file into memory as a string
-"""
-def slurp_file(filename):
- if filename is None:
+def read_textfile(filepath):
+ """
+ Read a whole file into memory as a string
+ """
+ if filepath is None:
return ''
- return ''.join(open(filename).readlines())
-"""
-Perform template substitution
-"""
-def process_template(template_file, variables):
- class PercentSignTemplate(string.Template):
- delimiter = '%'
+ with open(filepath) as f:
+ return ''.join(f.readlines())
- try:
- template = PercentSignTemplate(slurp_file(template_file))
- return template.substitute(variables)
- except KeyError as e:
- raise Exception('Unbound var %s in template %s' % (e, template_file))
-"""
-Create the template variables needed to process the makefile, build.h, etc
-"""
-def create_template_vars(build_config, options, modules, cc, arch, osinfo):
- def make_cpp_macros(macros):
- return '\n'.join(['#define BOTAN_' + macro for macro in macros])
+def process_template(template_file, variables):
+ # pylint: disable=too-many-branches,too-many-statements
"""
- Figure out what external libraries are needed based on selected modules
+ Perform template substitution
+
+ The template language supports (un-nested) conditionals.
"""
- def link_to():
- libs = set()
- for module in modules:
- for (osname,link_to) in module.libs.items():
- if osname == 'all' or osname == osinfo.basename:
- libs |= set(link_to)
+ class SimpleTemplate(object):
+
+ def __init__(self, vals):
+ self.vals = vals
+ self.value_pattern = re.compile(r'%{([a-z][a-z_0-9\|]+)}')
+ self.cond_pattern = re.compile('%{(if|unless) ([a-z][a-z_0-9]+)}')
+ self.for_pattern = re.compile('(.*)%{for ([a-z][a-z_0-9]+)}')
+ self.join_pattern = re.compile('(.*)%{join ([a-z][a-z_0-9]+)}')
+
+ def substitute(self, template):
+ # pylint: disable=too-many-locals
+ def insert_value(match):
+ v = match.group(1)
+ if v in self.vals:
+ return str(self.vals.get(v))
+ if v.endswith('|upper'):
+ v = v.replace('|upper', '')
+ if v in self.vals:
+ return str(self.vals.get(v)).upper()
+
+ raise KeyError(v)
+
+ lines = template.splitlines()
+
+ output = ""
+ idx = 0
+
+ while idx < len(lines):
+ cond_match = self.cond_pattern.match(lines[idx])
+ join_match = self.join_pattern.match(lines[idx])
+ for_match = self.for_pattern.match(lines[idx])
+
+ if cond_match:
+ cond_type = cond_match.group(1)
+ cond_var = cond_match.group(2)
+
+ include_cond = False
+
+ if cond_type == 'if' and cond_var in self.vals and self.vals.get(cond_var):
+ include_cond = True
+ elif cond_type == 'unless' and (cond_var not in self.vals or (not self.vals.get(cond_var))):
+ include_cond = True
+
+ idx += 1
+ while idx < len(lines):
+ if lines[idx] == '%{endif}':
+ break
+ if include_cond:
+ output += lines[idx] + "\n"
+ idx += 1
+ elif join_match:
+ join_var = join_match.group(2)
+ join_str = ' '
+ join_line = '%%{join %s}' % (join_var)
+ output += lines[idx].replace(join_line, join_str.join(self.vals[join_var])) + "\n"
+ elif for_match:
+ for_prefix = for_match.group(1)
+ output += for_prefix
+ for_var = for_match.group(2)
+
+ if for_var not in self.vals:
+ raise InternalError("Unknown for loop iteration variable '%s'" % (for_var))
+
+ var = self.vals[for_var]
+ if not isinstance(var, list):
+ raise InternalError("For loop iteration variable '%s' is not a list" % (for_var))
+ idx += 1
+
+ for_body = ""
+ while idx < len(lines):
+ if lines[idx] == '%{endfor}':
+ break
+ for_body += lines[idx] + "\n"
+ idx += 1
+
+ for v in var:
+ if isinstance(v, dict):
+ for_val = for_body
+ for ik, iv in v.items():
+ for_val = for_val.replace('%{' + ik + '}', iv)
+ output += for_val + "\n"
+ else:
+ output += for_body.replace('%{i}', v).replace('%{i|upper}', v.upper())
+ output += "\n"
else:
- match = re.match('^all!(.*)', osname)
- if match is not None:
- exceptions = match.group(1).split(',')
- if osinfo.basename not in exceptions:
- libs |= set(link_to)
- return sorted(libs)
+ output += lines[idx] + "\n"
+ idx += 1
- def objectfile_list(sources, obj_dir):
- for src in sources:
- (dir,file) = os.path.split(os.path.normpath(src))
+ return self.value_pattern.sub(insert_value, output) + '\n'
- if dir.startswith('src'):
- parts = dir.split(os.sep)[1:]
- if file == parts[-1] + '.cpp':
- name = '_'.join(dir.split(os.sep)[1:]) + '.cpp'
- else:
- name = '_'.join(dir.split(os.sep)[1:]) + '_' + file
+ try:
+ return SimpleTemplate(variables).substitute(read_textfile(template_file))
+ except KeyError as e:
+ logging.error('Unbound var %s in template %s' % (e, template_file))
+ except Exception as e: # pylint: disable=broad-except
+ logging.error('Exception %s during template processing file %s' % (e, template_file))
+
+def yield_objectfile_list(sources, obj_dir, obj_suffix):
+ obj_suffix = '.' + obj_suffix
+
+ for src in sources:
+ (directory, filename) = os.path.split(os.path.normpath(src))
+ parts = directory.split(os.sep)
+
+ if 'src' in parts:
+ parts = parts[parts.index('src')+2:]
+ elif filename.find('botan_all') != -1:
+ parts = []
+ else:
+ raise InternalError("Unexpected file '%s/%s'" % (directory, filename))
+
+ if parts != []:
+ # Handle src/X/X.cpp -> X.o
+ if filename == parts[-1] + '.cpp':
+ name = '_'.join(parts) + '.cpp'
else:
- name = file
+ name = '_'.join(parts) + '_' + filename
- for src_suffix in ['.cpp', '.S']:
- name = name.replace(src_suffix, '.' + osinfo.obj_suffix)
+ def fixup_obj_name(name):
+ def remove_dups(parts):
+ last = None
+ for part in parts:
+ if last is None or part != last:
+ last = part
+ yield part
- yield os.path.join(obj_dir, name)
+ return '_'.join(remove_dups(name.split('_')))
+ name = fixup_obj_name(name)
+ else:
+ name = filename
- def choose_mp_bits():
- mp_bits = [mod.mp_bits for mod in modules if mod.mp_bits != 0]
+ name = name.replace('.cpp', obj_suffix)
+ yield os.path.join(obj_dir, name)
- if mp_bits == []:
- return 32 # default
+def generate_build_info(build_paths, modules, cc, arch, osinfo, options):
+ # pylint: disable=too-many-locals
- # Check that settings are consistent across modules
- for mp_bit in mp_bits[1:]:
- if mp_bit != mp_bits[0]:
- raise Exception('Incompatible mp_bits settings found')
+ # first create a map of src_file->owning module
- return mp_bits[0]
+ module_that_owns = {}
- """
- Form snippets of makefile for building each source file
- """
- def build_commands(sources, obj_dir, flags):
- for (obj_file,src) in zip(objectfile_list(sources, obj_dir), sources):
- yield '%s: %s\n\t$(CXX) %s%s $(%s_FLAGS) %s$? %s$@\n' % (
- obj_file, src,
- cc.add_include_dir_option,
- build_config.include_dir,
- flags,
- cc.compile_option,
- cc.output_to_option)
-
- def makefile_list(items):
- items = list(items) # force evaluation so we can slice it
- return (' '*16).join([item + ' \\\n' for item in items[:-1]] +
- [items[-1]])
-
- def prefix_with_build_dir(path):
- if options.with_build_dir != None:
- return os.path.join(options.with_build_dir, path)
- return path
-
- def warning_flags(normal_flags,
- maintainer_flags,
- maintainer_mode):
- if maintainer_mode and maintainer_flags != '':
- return maintainer_flags
- return normal_flags
-
- return {
- 'version_major': build_config.version_major,
- 'version_minor': build_config.version_minor,
- 'version_patch': build_config.version_patch,
- 'version_vc_rev': build_config.version_vc_rev,
- 'so_abi_rev': build_config.version_so_rev,
- 'version': build_config.version_string,
+ for mod in modules:
+ for src in mod.sources():
+ module_that_owns[src] = mod
- 'distribution_info': options.distribution_info,
+ def _isa_specific_flags(src):
+ if os.path.basename(src) == 'test_simd.cpp':
+ return cc.get_isa_specific_flags(['simd'], arch, options)
- 'version_datestamp': build_config.version_datestamp,
+ if src in module_that_owns:
+ module = module_that_owns[src]
+ isas = module.need_isa
+ if 'simd' in module.dependencies(osinfo):
+ isas.append('simd')
- 'timestamp': build_config.timestamp(),
- 'user': build_config.username(),
- 'hostname': build_config.hostname(),
- 'command_line': ' '.join(sys.argv),
- 'local_config': slurp_file(options.local_config),
- 'makefile_style': options.makefile_style or cc.makefile_style,
+ return cc.get_isa_specific_flags(isas, arch, options)
- 'makefile_path': prefix_with_build_dir('Makefile'),
+ if src.startswith('botan_all_'):
+ isas = src.replace('botan_all_', '').replace('.cpp', '').split('_')
+ return cc.get_isa_specific_flags(isas, arch, options)
- 'prefix': options.prefix or osinfo.install_root,
- 'libdir': options.libdir or osinfo.lib_dir,
- 'includedir': options.includedir or osinfo.header_dir,
- 'docdir': options.docdir or osinfo.doc_dir,
+ return ''
- 'build_dir': build_config.build_dir,
- 'doc_output_dir': build_config.doc_output_dir,
+ def _build_info(sources, objects, target_type):
+ output = []
+ for (obj_file, src) in zip(objects, sources):
+ info = {
+ 'src': src,
+ 'obj': obj_file,
+ 'isa_flags': _isa_specific_flags(src)
+ }
- 'build_doc_commands': build_config.build_doc_commands,
+ if target_type == 'fuzzer':
+ fuzz_basename = os.path.basename(obj_file).replace('.' + osinfo.obj_suffix, '')
+ info['exe'] = os.path.join(build_paths.fuzzer_output_dir, fuzz_basename)
- 'python_dir': build_config.python_dir,
+ output.append(info)
- 'os': options.os,
- 'arch': options.arch,
- 'submodel': options.cpu,
+ return output
- 'mp_bits': choose_mp_bits(),
+ out = {}
- 'cc': (options.compiler_binary or cc.binary_name) +
- cc.mach_abi_link_flags(options.os, options.arch,
- options.cpu, options.debug_build),
+ targets = ['lib', 'cli', 'test', 'fuzzer']
- 'lib_opt': cc.library_opt_flags(options),
- 'mach_opt': cc.mach_opts(options.arch, options.cpu),
- 'check_opt': '' if options.no_optimizations else cc.check_opt_flags,
- 'lang_flags': cc.lang_flags + options.extra_flags,
- 'warn_flags': warning_flags(cc.warning_flags,
- cc.maintainer_warning_flags,
- options.maintainer_mode),
+ out['isa_build_info'] = []
- 'shared_flags': cc.gen_shared_flags(options),
- 'visibility_attribute': cc.gen_visibility_attribute(options),
+ fuzzer_bin = []
+ for t in targets:
+ src_list, src_dir = build_paths.src_info(t)
- 'so_link': cc.so_link_command_for(osinfo.basename),
+ src_key = '%s_srcs' % (t)
+ obj_key = '%s_objs' % (t)
+ build_key = '%s_build_info' % (t)
- 'link_to': ' '.join([cc.add_lib_option + lib for lib in link_to()]),
+ objects = []
+ build_info = []
- 'module_defines': make_cpp_macros(sorted(flatten([m.defines() for m in modules]))),
+ if src_list is not None:
+ src_list.sort()
+ objects = list(yield_objectfile_list(src_list, src_dir, osinfo.obj_suffix))
+ build_info = _build_info(src_list, objects, t)
- 'target_os_defines': make_cpp_macros(osinfo.defines()),
+ for b in build_info:
+ if b['isa_flags'] != '':
+ out['isa_build_info'].append(b)
- 'target_compiler_defines': make_cpp_macros(
- cc.defines(options.with_tr1)),
+ if t == 'fuzzer':
+ fuzzer_bin = [b['exe'] for b in build_info]
- 'target_cpu_defines': make_cpp_macros(arch.defines(options)),
+ out[src_key] = src_list if src_list else []
+ out[obj_key] = objects
+ out[build_key] = build_info
- 'include_files': makefile_list(build_config.public_headers),
+ out['fuzzer_bin'] = ' '.join(fuzzer_bin)
+ out['cli_headers'] = build_paths.cli_headers
- 'lib_objs': makefile_list(
- objectfile_list(build_config.build_sources,
- build_config.libobj_dir)),
+ return out
- 'check_objs': makefile_list(
- objectfile_list(build_config.check_sources,
- build_config.checkobj_dir)),
+def create_template_vars(source_paths, build_paths, options, modules, cc, arch, osinfo):
+ #pylint: disable=too-many-locals,too-many-branches,too-many-statements
- 'lib_build_cmds': '\n'.join(
- build_commands(build_config.build_sources,
- build_config.libobj_dir, 'LIB')),
+ """
+ Create the template variables needed to process the makefile, build.h, etc
+ """
- 'check_build_cmds': '\n'.join(
- build_commands(build_config.check_sources,
- build_config.checkobj_dir, 'CHECK')),
+ def external_link_cmd():
+ return (' ' + cc.add_lib_dir_option + options.with_external_libdir) if options.with_external_libdir else ''
- 'python_obj_dir': build_config.pyobject_dir,
+ def link_to(module_member_name):
+ """
+ Figure out what external libraries/frameworks are needed based on selected modules
+ """
+ if not (module_member_name == 'libs' or module_member_name == 'frameworks'):
+ raise InternalError("Invalid argument")
- 'python_objs': makefile_list(
- objectfile_list(build_config.python_sources,
- build_config.pyobject_dir)),
+ libs = set()
+ for module in modules:
+ for (osname, module_link_to) in getattr(module, module_member_name).items():
+ if osname == 'all' or osname == osinfo.basename:
+ libs |= set(module_link_to)
+ else:
+ match = re.match('^all!(.*)', osname)
+ if match is not None:
+ exceptions = match.group(1).split(',')
+ if osinfo.basename not in exceptions:
+ libs |= set(module_link_to)
- 'python_build_cmds': '\n'.join(
- build_commands(build_config.python_sources,
- build_config.pyobject_dir, 'PYTHON')),
+ return sorted(libs)
- 'ar_command': cc.ar_command or osinfo.ar_command,
- 'ranlib_command': osinfo.ranlib_command(),
- 'install_cmd_exec': osinfo.install_cmd_exec,
- 'install_cmd_data': osinfo.install_cmd_data,
+ def choose_mp_bits():
+ mp_bits = arch.wordsize # allow command line override?
+ logging.debug('Using MP bits %d' % (mp_bits))
+ return mp_bits
+
+ def innosetup_arch(os_name, arch):
+ if os_name == 'windows':
+ inno_arch = {'x86_32': '', 'x86_64': 'x64', 'ia64': 'ia64'}
+ if arch in inno_arch:
+ return inno_arch[arch]
+ else:
+ logging.warning('Unknown arch in innosetup_arch %s' % (arch))
+ return None
- 'check_prefix': prefix_with_build_dir(''),
- 'lib_prefix': prefix_with_build_dir(''),
+ def configure_command_line():
+ # Cut absolute path from main executable (e.g. configure.py or python interpreter)
+ # to get the same result when configuring the same thing on different machines
+ main_executable = os.path.basename(sys.argv[0])
+ return ' '.join([main_executable] + sys.argv[1:])
- 'static_suffix': osinfo.static_suffix,
- 'so_suffix': osinfo.so_suffix,
+ def cmake_escape(s):
+ return s.replace('(', '\\(').replace(')', '\\)')
- 'botan_config': prefix_with_build_dir(
- os.path.join(build_config.build_dir,
- build_config.config_shell_script())),
+ def sysroot_option():
+ if options.with_sysroot_dir == '':
+ return ''
+ if cc.add_sysroot_option == '':
+ logging.error("This compiler doesn't support --sysroot option")
+ return cc.add_sysroot_option + options.with_sysroot_dir
- 'botan_pkgconfig': prefix_with_build_dir(
- os.path.join(build_config.build_dir,
- build_config.pkg_config_file())),
+ def ar_command():
+ if options.ar_command:
+ return options.ar_command
- 'mod_list': '\n'.join(sorted([m.basename for m in modules])),
+ if cc.ar_command:
+ if cc.ar_command == cc.binary_name:
+ return options.compiler_binary or cc.binary_name
+ else:
+ return cc.ar_command
- 'python_version': options.python_version
- }
+ return osinfo.ar_command
-"""
-Determine which modules to load based on options, target, etc
-"""
-def choose_modules_to_use(modules, archinfo, options):
+ def choose_endian(arch_info, options):
+ if options.with_endian != None:
+ return options.with_endian
- for mod in modules.values():
- mod.dependencies_exist(modules)
+ if options.cpu.endswith('eb') or options.cpu.endswith('be'):
+ return 'big'
+ elif options.cpu.endswith('el') or options.cpu.endswith('le'):
+ return 'little'
- to_load = []
- maybe_dep = []
- not_using_because = {}
+ logging.info('Defaulting to assuming %s endian', arch_info.endian)
+ return arch_info.endian
- def cannot_use_because(mod, reason):
- not_using_because.setdefault(reason, []).append(mod)
+ build_dir = options.with_build_dir or os.path.curdir
+ program_suffix = options.program_suffix or osinfo.program_suffix
- for modname in options.enabled_modules:
- if modname not in modules:
- logging.warning("Unknown enabled module %s" % (modname))
+ def join_with_build_dir(path):
+ # For some unknown reason MinGW doesn't like ./foo
+ if build_dir == os.path.curdir and options.os == 'mingw':
+ return path
+ return os.path.join(build_dir, path)
- for modname in options.disabled_modules:
- if modname not in modules:
- logging.warning("Unknown disabled module %s" % (modname))
+ def shared_lib_uses_symlinks():
+ if options.os in ['windows', 'openbsd']:
+ return False
+ return True
- for (modname, module) in modules.items():
- if modname in options.disabled_modules:
- cannot_use_because(modname, 'disabled by user')
- elif modname in options.enabled_modules:
- to_load.append(modname) # trust the user
+ variables = {
+ 'version_major': Version.major(),
+ 'version_minor': Version.minor(),
+ 'version_patch': Version.patch(),
+ 'version_vc_rev': Version.vc_rev(),
+ 'abi_rev': Version.so_rev(),
- elif not module.compatible_os(options.os):
- cannot_use_because(modname, 'incompatible OS')
- elif not module.compatible_compiler(options.compiler):
- cannot_use_because(modname, 'incompatible compiler')
- elif not module.compatible_cpu(archinfo, options):
- cannot_use_because(modname, 'incompatible CPU')
- elif not module.tr1_ok(options.with_tr1):
- cannot_use_because(modname, 'missing TR1')
+ 'version': Version.as_string(),
+ 'release_type': Version.release_type(),
+ 'version_datestamp': Version.datestamp(),
- else:
- if module.load_on == 'never':
- cannot_use_because(modname, 'disabled as buggy')
- elif module.load_on == 'request':
- cannot_use_because(modname, 'by request only')
- elif module.load_on == 'dep':
- maybe_dep.append(modname)
-
- elif module.load_on == 'always':
- to_load.append(modname)
-
- elif module.load_on == 'asm_ok':
- if options.asm_ok:
- if options.no_autoload:
- maybe_dep.append(modname)
- else:
- to_load.append(modname)
- else:
- cannot_use_because(modname,
- 'uses assembly and --disable-asm set')
- elif module.load_on == 'auto':
- if options.no_autoload:
- maybe_dep.append(modname)
- else:
- to_load.append(modname)
- else:
- logging.warning('Unknown load_on %s in %s' % (
- module.load_on, modname))
+ 'distribution_info': options.distribution_info,
- dependency_failure = True
+ 'darwin_so_compat_ver': '%s.%s.0' % (Version.packed(), Version.so_rev()),
+ 'darwin_so_current_ver': '%s.%s.%s' % (Version.packed(), Version.so_rev(), Version.patch()),
- while dependency_failure:
- dependency_failure = False
- for modname in to_load:
- for deplist in [s.split('|') for s in modules[modname].dependencies()]:
+ 'base_dir': source_paths.base_dir,
+ 'src_dir': source_paths.src_dir,
+ 'doc_dir': source_paths.doc_dir,
+ 'scripts_dir': normalize_source_path(source_paths.scripts_dir),
+ 'python_dir': source_paths.python_dir,
- dep_met = False
- for mod in deplist:
- if dep_met is True:
- break
+ 'cli_exe_name': osinfo.cli_exe_name + program_suffix,
+ 'cli_exe': join_with_build_dir(osinfo.cli_exe_name + program_suffix),
+ 'test_exe': join_with_build_dir('botan-test' + program_suffix),
+
+ 'lib_prefix': osinfo.lib_prefix,
+ 'static_suffix': osinfo.static_suffix,
+ 'lib_suffix': options.library_suffix,
+ 'libname': osinfo.library_name.format(major=Version.major(),
+ minor=Version.minor(),
+ suffix=options.library_suffix),
- if mod in to_load:
- dep_met = True
- elif mod in maybe_dep:
- maybe_dep.remove(mod)
- to_load.append(mod)
- dep_met = True
+ 'command_line': configure_command_line(),
+ 'local_config': read_textfile(options.local_config),
- if dep_met == False:
- dependency_failure = True
- if modname in to_load:
- to_load.remove(modname)
- if modname in maybe_dep:
- maybe_dep.remove(modname)
- cannot_use_because(modname, 'dependency failure')
+ 'program_suffix': program_suffix,
- for not_a_dep in maybe_dep:
- cannot_use_because(not_a_dep, 'loaded only if needed by dependency')
+ 'prefix': options.prefix or osinfo.install_root,
+ 'bindir': options.bindir or osinfo.bin_dir,
+ 'libdir': options.libdir or osinfo.lib_dir,
+ 'mandir': options.mandir or osinfo.man_dir,
+ 'includedir': options.includedir or osinfo.header_dir,
+ 'docdir': options.docdir or osinfo.doc_dir,
- for reason in sorted(not_using_because.keys()):
- disabled_mods = sorted(set([mod for mod in not_using_because[reason]]))
+ 'with_documentation': options.with_documentation,
+ 'with_sphinx': options.with_sphinx,
+ 'with_pdf': options.with_pdf,
+ 'with_rst2man': options.with_rst2man,
+ 'sphinx_config_dir': source_paths.sphinx_config_dir,
+ 'with_doxygen': options.with_doxygen,
- if disabled_mods != []:
- logging.info('Skipping, %s - %s' % (
- reason, ' '.join(disabled_mods)))
+ 'out_dir': build_dir,
+ 'build_dir': build_paths.build_dir,
- for mod in sorted(to_load):
- if mod.startswith('mp_'):
- logging.info('Using MP module ' + mod)
- if mod.startswith('simd_') and mod != 'simd_engine':
- logging.info('Using SIMD module ' + mod)
- if modules[mod].comment:
- logging.info('%s: %s' % (mod, modules[mod].comment))
+ 'doc_stamp_file': os.path.join(build_paths.build_dir, 'doc.stamp'),
+ 'makefile_path': os.path.join(build_paths.build_dir, '..', 'Makefile'),
- logging.debug('Loading modules %s', ' '.join(sorted(to_load)))
+ 'build_static_lib': options.build_static_lib,
+ 'build_shared_lib': options.build_shared_lib,
- return [modules[mod] for mod in to_load]
+ 'build_fuzzers': options.build_fuzzers,
-"""
-Load the info files about modules, targets, etc
-"""
-def load_info_files(options):
+ 'build_coverage' : options.with_coverage_info or options.with_coverage,
- def find_files_named(desired_name, in_path):
- for (dirpath, dirnames, filenames) in os.walk(in_path):
- if desired_name in filenames:
- yield os.path.join(dirpath, desired_name)
+ 'symlink_shared_lib': options.build_shared_lib and shared_lib_uses_symlinks(),
- modules = dict([(mod.basename, mod) for mod in
- [ModuleInfo(info) for info in
- find_files_named('info.txt', options.src_dir)]])
+ 'libobj_dir': build_paths.libobj_dir,
+ 'cliobj_dir': build_paths.cliobj_dir,
+ 'testobj_dir': build_paths.testobj_dir,
+ 'fuzzobj_dir': build_paths.fuzzobj_dir,
- def list_files_in_build_data(subdir):
- for (dirpath, dirnames, filenames) in \
- os.walk(os.path.join(options.build_data, subdir)):
- for filename in filenames:
- if filename.endswith('.txt'):
- yield os.path.join(dirpath, filename)
+ 'fuzzer_output_dir': build_paths.fuzzer_output_dir if build_paths.fuzzer_output_dir else '',
+ 'doc_output_dir': build_paths.doc_output_dir,
+ 'doc_output_dir_manual': build_paths.doc_output_dir_manual,
+ 'doc_output_dir_doxygen': build_paths.doc_output_dir_doxygen,
- def form_name(filepath):
- return os.path.basename(filepath).replace('.txt', '')
+ 'os': options.os,
+ 'arch': options.arch,
+ 'cpu_family': arch.family,
+ 'endian': choose_endian(arch, options),
+ 'cpu_is_64bit': arch.wordsize == 64,
- archinfo = dict([(form_name(info), ArchInfo(info))
- for info in list_files_in_build_data('arch')])
+ 'bakefile_arch': 'x86' if options.arch == 'x86_32' else 'x86_64',
- osinfo = dict([(form_name(info), OsInfo(info))
- for info in list_files_in_build_data('os')])
+ 'innosetup_arch': innosetup_arch(options.os, options.arch),
- ccinfo = dict([(form_name(info), CompilerInfo(info))
- for info in list_files_in_build_data('cc')])
+ 'mp_bits': choose_mp_bits(),
- def info_file_load_report(type, num):
- if num > 0:
- logging.debug('Loaded %d %s info files' % (num, type))
- else:
- logging.warning('Failed to load any %s info files' % (type))
+ 'python_exe': os.path.basename(sys.executable),
+ 'python_version': options.python_version,
- info_file_load_report('CPU', len(archinfo));
- info_file_load_report('OS', len(osinfo))
- info_file_load_report('compiler', len(ccinfo))
+ 'cxx': (options.compiler_binary or cc.binary_name),
+ 'cxx_abi_flags': cc.mach_abi_link_flags(options),
+ 'linker': cc.linker_name or '$(CXX)',
+ 'make_supports_phony': cc.basename != 'msvc',
- return (modules, archinfo, ccinfo, osinfo)
+ 'sanitizer_types' : sorted(cc.sanitizer_types),
-"""
-Perform the filesystem operations needed to setup the build
-"""
-def setup_build(build_config, options, template_vars):
+ 'cc_compile_opt_flags': cc.cc_compile_flags(options, False, True),
+ 'cc_compile_debug_flags': cc.cc_compile_flags(options, True, False),
- """
- Choose the link method based on system availablity and user request
- """
- def choose_link_method(req_method):
+ # These are for CMake
+ 'cxx_abi_opt_flags': cc.mach_abi_link_flags(options, False),
+ 'cxx_abi_debug_flags': cc.mach_abi_link_flags(options, True),
- def useable_methods():
- if 'symlink' in os.__dict__:
- yield 'symlink'
- if 'link' in os.__dict__:
- yield 'hardlink'
- yield 'copy'
+ 'dash_o': cc.output_to_object,
+ 'dash_c': cc.compile_flags,
- for method in useable_methods():
- if req_method is None or req_method == method:
- return method
+ 'cc_lang_flags': cc.cc_lang_flags(),
+ 'cc_sysroot': sysroot_option(),
+ 'cc_compile_flags': options.cxxflags or cc.cc_compile_flags(options),
+ 'ldflags': options.ldflags or '',
+ 'cc_warning_flags': cc.cc_warning_flags(options),
+ 'output_to_exe': cc.output_to_exe,
+ 'cc_macro': cc.macro_name,
- logging.info('Could not use requested link method %s' % (req_method))
- return 'copy'
+ 'shared_flags': cc.gen_shared_flags(options),
+ 'cmake_shared_flags': cmake_escape(cc.gen_shared_flags(options)),
+ 'visibility_attribute': cc.gen_visibility_attribute(options),
- """
- Copy or link the file, depending on what the platform offers
- """
- def portable_symlink(filename, target_dir, method):
+ 'lib_link_cmd': cc.so_link_command_for(osinfo.basename, options) + external_link_cmd(),
+ 'exe_link_cmd': cc.binary_link_command_for(osinfo.basename, options) + external_link_cmd(),
+ 'post_link_cmd': '',
- if not os.access(filename, os.R_OK):
- logging.warning('Missing file %s' % (filename))
- return
+ 'ar_command': ar_command(),
+ 'ar_options': cc.ar_options or osinfo.ar_options,
+ 'ar_output_to': cc.ar_output_to,
+
+ 'link_to': ' '.join(
+ [cc.add_lib_option + lib for lib in link_to('libs')] +
+ [cc.add_framework_option + fw for fw in link_to('frameworks')]
+ ),
+
+ 'cmake_link_to': ' '.join(
+ [lib for lib in link_to('libs')] +
+ [('"' + cc.add_framework_option + fw + '"') for fw in link_to('frameworks')]
+ ),
+
+ 'fuzzer_lib': (cc.add_lib_option + options.fuzzer_lib) if options.fuzzer_lib else '',
+ 'libs_used': [lib.replace('.lib', '') for lib in link_to('libs')],
- if method == 'symlink':
- def count_dirs(dir, accum = 0):
- if dir in ['', '/', os.path.curdir]:
- return accum
- (dir,basename) = os.path.split(dir)
- return accum + 1 + count_dirs(dir)
+ 'include_paths': build_paths.format_include_paths(cc, options.with_external_includedir),
+ 'module_defines': sorted(flatten([m.defines() for m in modules])),
- dirs_up = count_dirs(target_dir)
+ 'os_features': osinfo.enabled_features(options),
+ 'os_name': osinfo.basename,
+ 'cpu_features': arch.supported_isa_extensions(cc, options),
+
+ 'fuzzer_mode': options.unsafe_fuzzer_mode,
+ 'fuzzer_type': options.build_fuzzers.upper() if options.build_fuzzers else '',
+
+ 'with_valgrind': options.with_valgrind,
+ 'with_openmp': options.with_openmp,
+ 'with_debug_asserts': options.with_debug_asserts,
+ 'test_mode': options.test_mode,
+
+ 'mod_list': sorted([m.basename for m in modules])
+ }
- source = os.path.join(os.path.join(*[os.path.pardir]*dirs_up),
- filename)
+ if options.os != 'windows':
+ variables['botan_pkgconfig'] = os.path.join(build_paths.build_dir, 'botan-%d.pc' % (Version.major()))
- target = os.path.join(target_dir, os.path.basename(filename))
+ # The name is always set because Windows build needs it
+ variables['static_lib_name'] = '%s%s.%s' % (variables['lib_prefix'], variables['libname'],
+ variables['static_suffix'])
+
+ if options.build_shared_lib:
+ if osinfo.soname_pattern_base != None:
+ variables['soname_base'] = osinfo.soname_pattern_base.format(**variables)
+ variables['shared_lib_name'] = variables['soname_base']
+
+ if osinfo.soname_pattern_abi != None:
+ variables['soname_abi'] = osinfo.soname_pattern_abi.format(**variables)
+ variables['shared_lib_name'] = variables['soname_abi']
+
+ if osinfo.soname_pattern_patch != None:
+ variables['soname_patch'] = osinfo.soname_pattern_patch.format(**variables)
+
+ variables['lib_link_cmd'] = variables['lib_link_cmd'].format(**variables)
+ variables['post_link_cmd'] = osinfo.so_post_link_command.format(**variables) if options.build_shared_lib else ''
+
+ lib_targets = []
+ if options.build_static_lib:
+ lib_targets.append('static_lib_name')
+ if options.build_shared_lib:
+ lib_targets.append('shared_lib_name')
+
+ variables['library_targets'] = ' '.join([join_with_build_dir(variables[t]) for t in lib_targets])
+
+ if options.os == 'llvm' or options.compiler == 'msvc':
+ # llvm-link and msvc require just naming the file directly
+ variables['link_to_botan'] = os.path.join(build_dir, variables['static_lib_name'])
+ else:
+ variables['link_to_botan'] = '%s%s %s%s' % (cc.add_lib_dir_option, build_dir,
+ cc.add_lib_option, variables['libname'])
- os.symlink(source, target)
+ return variables
- elif method == 'hardlink':
- os.link(filename,
- os.path.join(target_dir, os.path.basename(filename)))
+class ModulesChooser(object):
+ """
+ Determine which modules to load based on options, target, etc
+ """
- elif method == 'copy':
- shutil.copy(filename, target_dir)
+ def __init__(self, modules, module_policy, archinfo, osinfo, ccinfo, cc_min_version, options):
+ self._modules = modules
+ self._module_policy = module_policy
+ self._archinfo = archinfo
+ self._osinfo = osinfo
+ self._ccinfo = ccinfo
+ self._cc_min_version = cc_min_version
+ self._options = options
+
+ self._maybe_dep = set()
+ self._to_load = set()
+ # string to set mapping with reasons as key and modules as value
+ self._not_using_because = collections.defaultdict(set)
+
+ ModulesChooser._validate_dependencies_exist(self._modules)
+ ModulesChooser._validate_user_selection(
+ self._modules, self._options.enabled_modules, self._options.disabled_modules)
+
+ def _check_usable(self, module, modname):
+ if not module.compatible_os(self._osinfo, self._options):
+ self._not_using_because['incompatible OS'].add(modname)
+ return False
+ elif not module.compatible_compiler(self._ccinfo, self._cc_min_version, self._archinfo.basename):
+ self._not_using_because['incompatible compiler'].add(modname)
+ return False
+ elif not module.compatible_cpu(self._archinfo, self._options):
+ self._not_using_because['incompatible CPU'].add(modname)
+ return False
+ return True
+ @staticmethod
+ def _display_module_information_unused(skipped_modules):
+ for reason in sorted(skipped_modules.keys()):
+ disabled_mods = sorted(skipped_modules[reason])
+ if disabled_mods:
+ logging.info('Skipping (%s): %s' % (reason, ' '.join(disabled_mods)))
+
+ @staticmethod
+ def _display_module_information_to_load(all_modules, modules_to_load):
+ sorted_modules_to_load = sorted(modules_to_load)
+
+ for modname in sorted_modules_to_load:
+ if modname.startswith('simd_') and modname != 'simd_engine':
+ logging.info('Using SIMD module ' + modname)
+
+ for modname in sorted_modules_to_load:
+ if all_modules[modname].comment:
+ logging.info('%s: %s' % (modname, all_modules[modname].comment))
+ if all_modules[modname].warning:
+ logging.warning('%s: %s' % (modname, all_modules[modname].warning))
+ if all_modules[modname].load_on == 'vendor':
+ logging.info('Enabling use of external dependency %s' % modname)
+
+ if sorted_modules_to_load:
+ logging.info('Loading modules: %s', ' '.join(sorted_modules_to_load))
else:
- raise Exception('Unknown link method %s' % (method))
+ logging.error('This configuration disables every submodule and is invalid')
+
+ @staticmethod
+ def _validate_state(used_modules, unused_modules):
+ for reason, unused_for_reason in unused_modules.items():
+ intersection = unused_for_reason & used_modules
+ if intersection:
+ raise InternalError(
+ "Disabled modules (%s) and modules to load have common elements: %s"
+ % (reason, intersection))
+
+ @staticmethod
+ def _validate_dependencies_exist(modules):
+ for module in modules.values():
+ module.dependencies_exist(modules)
+
+ @staticmethod
+ def _validate_user_selection(modules, enabled_modules, disabled_modules):
+ for modname in enabled_modules:
+ if modname not in modules:
+ logging.error("Module not found: %s" % modname)
+
+ for modname in disabled_modules:
+ if modname not in modules:
+ logging.warning("Disabled module not found: %s" % modname)
+
+ def _handle_by_module_policy(self, modname, usable):
+ if self._module_policy is not None:
+ if modname in self._module_policy.required:
+ if not usable:
+ logging.error('Module policy requires module %s not usable on this platform' % (modname))
+ elif modname in self._options.disabled_modules:
+ logging.error('Module %s was disabled but is required by policy' % (modname))
+ self._to_load.add(modname)
+ return True
+ elif modname in self._module_policy.if_available:
+ if modname in self._options.disabled_modules:
+ self._not_using_because['disabled by user'].add(modname)
+ elif usable:
+ logging.debug('Enabling optional module %s' % (modname))
+ self._to_load.add(modname)
+ return True
+ elif modname in self._module_policy.prohibited:
+ if modname in self._options.enabled_modules:
+ logging.error('Module %s was requested but is prohibited by policy' % (modname))
+ self._not_using_because['prohibited by module policy'].add(modname)
+ return True
- def choose_makefile_template(style):
- if style == 'nmake':
- return 'nmake.in'
- elif style == 'unix':
- return ('unix_shr.in' if options.build_shared_lib else 'unix.in')
+ return False
+
+ @staticmethod
+ def resolve_dependencies(available_modules, dependency_table, module, loaded_modules=None):
+ """
+ Parameters
+ - available_modules: modules to choose from. Constant.
+ - dependency_table: module to dependencies map. Constant.
+ - module: name of the module to resolve dependencies. Constant.
+ - loaded_modules: modules already loaded. Defensive copy in order to not change value for caller.
+ """
+ if loaded_modules is None:
+ loaded_modules = set([])
else:
- raise Exception('Unknown makefile style "%s"' % (style))
+ loaded_modules = copy.copy(loaded_modules)
- # First delete the build tree, if existing
- try:
- if options.clean_build_tree:
- shutil.rmtree(build_config.build_dir)
- except OSError as e:
- if e.errno != errno.ENOENT:
- logging.error('Problem while removing build dir: %s' % (e))
+ if module not in available_modules:
+ return False, None
- for dir in build_config.build_dirs:
- try:
- os.makedirs(dir)
- except OSError as e:
- if e.errno != errno.EEXIST:
- logging.error('Error while creating "%s": %s' % (dir, e))
+ loaded_modules.add(module)
+ for dependency in dependency_table[module]:
+ dependency_choices = set(dependency.split('|'))
- makefile_template = os.path.join(
- options.makefile_dir,
- choose_makefile_template(template_vars['makefile_style']))
+ dependency_met = False
- logging.debug('Using makefile template %s' % (makefile_template))
+ if not set(dependency_choices).isdisjoint(loaded_modules):
+ dependency_met = True
+ else:
+ possible_mods = dependency_choices.intersection(available_modules)
+
+ for mod in possible_mods:
+ ok, dependency_modules = ModulesChooser.resolve_dependencies(
+ available_modules, dependency_table, mod, loaded_modules)
+ if ok:
+ dependency_met = True
+ loaded_modules.add(mod)
+ loaded_modules.update(dependency_modules)
+ break
- templates_to_proc = {
- makefile_template: template_vars['makefile_path']
- }
+ if not dependency_met:
+ return False, None
- def templates_to_use():
- yield (options.build_data, 'buildh.in', 'build.h')
- yield (options.build_data, 'botan.doxy.in', 'botan.doxy')
+ return True, loaded_modules
- if options.os != 'windows':
- yield (options.build_data, 'botan.pc.in', build_config.pkg_config_file())
- yield (options.build_data, 'botan-config.in', build_config.config_shell_script())
+ def _modules_dependency_table(self):
+ out = {}
+ for modname in self._modules:
+ out[modname] = self._modules[modname].dependencies(self._osinfo)
+ return out
- if options.os == 'windows':
- yield (options.build_data, 'innosetup.in', 'botan.iss')
+ def _resolve_dependencies_for_all_modules(self):
+ available_modules = set(self._to_load) | set(self._maybe_dep)
+ dependency_table = self._modules_dependency_table()
- if options.boost_python:
- yield (options.makefile_dir, 'python.in', 'Makefile.python')
+ successfully_loaded = set()
- for (template_dir, template, sink) in templates_to_use():
- source = os.path.join(template_dir, template)
- if template_dir == options.build_data:
- sink = os.path.join(build_config.build_dir, sink)
- templates_to_proc[source] = sink
+ for modname in self._to_load:
+ # This will try to recursively load all dependencies of modname
+ ok, modules = self.resolve_dependencies(available_modules, dependency_table, modname)
+ if ok:
+ successfully_loaded.add(modname)
+ successfully_loaded.update(modules)
+ else:
+ # Skip this module
+ pass
+
+ self._not_using_because['dependency failure'].update(self._to_load - successfully_loaded)
+ self._to_load = successfully_loaded
+ self._maybe_dep -= successfully_loaded
+
+ def _handle_by_load_on(self, module): # pylint: disable=too-many-branches
+ modname = module.basename
+ if module.load_on == 'never':
+ self._not_using_because['disabled as buggy'].add(modname)
+ elif module.load_on == 'request':
+ if self._options.with_everything:
+ self._to_load.add(modname)
+ else:
+ self._not_using_because['by request only'].add(modname)
+ elif module.load_on == 'vendor':
+ if self._options.with_everything:
+ self._to_load.add(modname)
+ else:
+ self._not_using_because['requires external dependency'].add(modname)
+ elif module.load_on == 'dep':
+ self._maybe_dep.add(modname)
- for (template, sink) in templates_to_proc.items():
- try:
- f = open(sink, 'w')
- f.write(process_template(template, template_vars))
- finally:
- f.close()
+ elif module.load_on == 'always':
+ self._to_load.add(modname)
- link_method = choose_link_method(options.link_method)
- logging.info('Using %s to link files into build directory' % (link_method))
+ elif module.load_on == 'auto':
+ if self._options.no_autoload or self._module_policy is not None:
+ self._maybe_dep.add(modname)
+ else:
+ self._to_load.add(modname)
+ else:
+ logging.error('Unknown load_on %s in %s' % (
+ module.load_on, modname))
- def link_headers(header_list, type, dir):
- logging.debug('Linking %d %s header files in %s' % (
- len(header_list), type, dir))
+ def choose(self):
+ for (modname, module) in self._modules.items():
+ usable = self._check_usable(module, modname)
- for header_file in header_list:
- try:
- portable_symlink(header_file, dir, link_method)
- except OSError as e:
- if e.errno != errno.EEXIST:
- logging.error('Error linking %s into %s: %s' % (
- header_file, dir, e))
+ module_handled = self._handle_by_module_policy(modname, usable)
+ if module_handled:
+ continue
+
+ if modname in self._options.disabled_modules:
+ self._not_using_because['disabled by user'].add(modname)
+ elif usable:
+ if modname in self._options.enabled_modules:
+ self._to_load.add(modname) # trust the user
+ else:
+ self._handle_by_load_on(module)
- link_headers(build_config.public_headers, 'public',
- build_config.botan_include_dir)
+ if 'compression' in self._to_load:
+ # Confirm that we have at least one compression library enabled
+ # Otherwise we leave a lot of useless support code compiled in, plus a
+ # make_compressor call that always fails
+ if 'zlib' not in self._to_load and 'bzip2' not in self._to_load and 'lzma' not in self._to_load:
+ self._to_load.remove('compression')
+ self._not_using_because['no enabled compression schemes'].add('compression')
- link_headers(build_config.build_internal_headers, 'internal',
- build_config.internal_include_dir)
+ self._resolve_dependencies_for_all_modules()
-"""
-Generate Amalgamation
-"""
-def generate_amalgamation(build_config):
- def ending_with_suffix(suffix):
- def predicate(val):
- return val.endswith(suffix)
- return predicate
-
- def strip_header_goop(header_name, contents):
- header_guard = re.compile('^#define BOTAN_.*_H__$')
-
- while len(contents) > 0:
- if header_guard.match(contents[0]):
- contents = contents[1:]
- break
+ for not_a_dep in self._maybe_dep:
+ self._not_using_because['not requested'].add(not_a_dep)
- contents = contents[1:]
+ ModulesChooser._validate_state(self._to_load, self._not_using_because)
+ ModulesChooser._display_module_information_unused(self._not_using_because)
+ ModulesChooser._display_module_information_to_load(self._modules, self._to_load)
- if len(contents) == 0:
- raise Exception("No header guard found in " + header_name)
+ return self._to_load
- while contents[0] == '\n':
- contents = contents[1:]
+def choose_link_method(options):
+ """
+ Choose the link method based on system availability and user request
+ """
- while contents[-1] == '\n':
- contents = contents[0:-1]
- if contents[-1] == '#endif\n':
- contents = contents[0:-1]
+ req = options.link_method
+
+ def useable_methods():
+ # Symbolic link support on Windows was introduced in Windows 6.0 (Vista) and Python 3.2
+ # Furthermore the SeCreateSymbolicLinkPrivilege is required in order to successfully create symlinks
+ # So only try to use symlinks on Windows if explicitly requested
+ if req == 'symlink' and options.os == 'windows':
+ yield 'symlink'
+ # otherwise keep old conservative behavior
+ if 'symlink' in os.__dict__ and options.os != 'windows':
+ yield 'symlink'
+ if 'link' in os.__dict__:
+ yield 'hardlink'
+ yield 'copy'
+
+ for method in useable_methods():
+ if req is None or req == method:
+ logging.info('Using %s to link files into build dir ' \
+ '(use --link-method to change)' % (method))
+ return method
+
+ logging.warning('Could not use link method "%s", will copy instead' % (req))
+ return 'copy'
+
+def portable_symlink(file_path, target_dir, method):
+ """
+ Copy or link the file, depending on what the platform offers
+ """
- return contents
+ if not os.access(file_path, os.R_OK):
+ logging.warning('Missing file %s' % (file_path))
+ return
+
+ if method == 'symlink':
+ rel_file_path = os.path.relpath(file_path, start=target_dir)
+ os.symlink(rel_file_path, os.path.join(target_dir, os.path.basename(file_path)))
+ elif method == 'hardlink':
+ os.link(file_path, os.path.join(target_dir, os.path.basename(file_path)))
+ elif method == 'copy':
+ shutil.copy(file_path, target_dir)
+ else:
+ raise UserError('Unknown link method %s' % (method))
- botan_include = re.compile('#include <botan/(.*)>$')
- std_include = re.compile('#include <([^/\.]+)>$')
- class Amalgamation_Generator:
- def __init__(self, input_list):
+class AmalgamationHelper(object):
+ # All include types may have trailing comment like e.g. '#include <vector> // IWYU pragma: export'
+ _any_include = re.compile(r'#include <(.*)>')
+ _botan_include = re.compile(r'#include <botan/(.*)>')
- self.included_already = set()
- self.all_std_includes = set()
+ # Only matches at the beginning of the line. By convention, this means that the include
+ # is not wrapped by condition macros
+ _unconditional_any_include = re.compile(r'^#include <(.*)>')
+ _unconditional_std_include = re.compile(r'^#include <([^/\.]+|stddef.h)>')
- self.file_contents = {}
- for f in sorted(input_list):
- contents = strip_header_goop(f, open(f).readlines())
- self.file_contents[os.path.basename(f)] = contents
+ @staticmethod
+ def is_any_include(cpp_source_line):
+ match = AmalgamationHelper._any_include.search(cpp_source_line)
+ if match:
+ return match.group(1)
+ else:
+ return None
- self.contents = ''
- for name in self.file_contents:
- self.contents += ''.join(list(self.header_contents(name)))
+ @staticmethod
+ def is_botan_include(cpp_source_line):
+ match = AmalgamationHelper._botan_include.search(cpp_source_line)
+ if match:
+ return match.group(1)
+ else:
+ return None
- self.header_includes = ''
- for std_header in self.all_std_includes:
- self.header_includes += '#include <%s>\n' % (std_header)
- self.header_includes += '\n'
+ @staticmethod
+ def is_unconditional_any_include(cpp_source_line):
+ match = AmalgamationHelper._unconditional_any_include.search(cpp_source_line)
+ if match:
+ return match.group(1)
+ else:
+ return None
- def header_contents(self, name):
- name = name.replace('internal/', '')
+ @staticmethod
+ def is_unconditional_std_include(cpp_source_line):
+ match = AmalgamationHelper._unconditional_std_include.search(cpp_source_line)
+ if match:
+ return match.group(1)
+ else:
+ return None
- if name in self.included_already:
- return
- self.included_already.add(name)
+class AmalgamationHeader(object):
+ def __init__(self, input_filepaths):
- if name not in self.file_contents:
- return
+ self.included_already = set()
+ self.all_std_includes = set()
- for line in self.file_contents[name]:
- match = botan_include.search(line)
- if match:
- for c in self.header_contents(match.group(1)):
- yield c
- else:
- match = std_include.search(line)
+ encoding_kwords = {}
+ if sys.version_info[0] == 3:
+ encoding_kwords['encoding'] = 'utf8'
- if match and match.group(1) != 'functional':
- self.all_std_includes.add(match.group(1))
- else:
- yield line
+ self.file_contents = {}
+ for filepath in sorted(input_filepaths):
+ try:
+ with open(filepath, **encoding_kwords) as f:
+ raw_content = f.readlines()
+ contents = AmalgamationGenerator.strip_header_goop(filepath, raw_content)
+ self.file_contents[os.path.basename(filepath)] = contents
+ except IOError as e:
+ logging.error('Error processing file %s for amalgamation: %s' % (filepath, e))
+
+ self.contents = ''
+ for name in sorted(self.file_contents):
+ self.contents += ''.join(list(self.header_contents(name)))
+
+ self.header_includes = ''
+ for std_header in sorted(self.all_std_includes):
+ self.header_includes += '#include <%s>\n' % (std_header)
+ self.header_includes += '\n'
+
+ def header_contents(self, name):
+ name = name.replace('internal/', '')
+
+ if name in self.included_already:
+ return
- amalg_basename = 'botan_all'
+ if name == 'botan.h':
+ return
- header_name = '%s.h' % (amalg_basename)
+ self.included_already.add(name)
- botan_h = open(header_name, 'w')
+ if name not in self.file_contents:
+ return
- pub_header_amalag = Amalgamation_Generator(build_config.public_headers)
+ for line in self.file_contents[name]:
+ header = AmalgamationHelper.is_botan_include(line)
+ if header:
+ for c in self.header_contents(header):
+ yield c
+ else:
+ std_header = AmalgamationHelper.is_unconditional_std_include(line)
- amalg_header = """/*
+ if std_header:
+ self.all_std_includes.add(std_header)
+ else:
+ yield line
+
+ @staticmethod
+ def write_banner(fd):
+ fd.write("""/*
* Botan %s Amalgamation
-* (C) 1999-2011 Jack Lloyd and others
+* (C) 1999-2018 The Botan Authors
*
-* Distributed under the terms of the Botan license
+* Botan is released under the Simplified BSD License (see license.txt)
*/
-""" % (build_config.version_string)
+""" % (Version.as_string()))
- botan_h.write(amalg_header)
+ @staticmethod
+ def _write_start_include_guard(fd, title):
+ fd.write("""
+#ifndef %s
+#define %s
- botan_h.write("""
-#ifndef BOTAN_AMALGAMATION_H__
-#define BOTAN_AMALGAMATION_H__
+""" % (title, title))
-""")
+ @staticmethod
+ def _write_end_include_guard(fd, title):
+ fd.write("\n#endif // %s\n" % (title))
- botan_h.write(pub_header_amalag.header_includes)
- botan_h.write(pub_header_amalag.contents)
- botan_h.write("\n#endif\n")
+ def write_to_file(self, filepath, include_guard):
+ with open(filepath, 'w') as f:
+ self.write_banner(f)
+ self._write_start_include_guard(f, include_guard)
+ f.write(self.header_includes)
+ f.write(self.contents)
+ self._write_end_include_guard(f, include_guard)
- internal_header_amalag = Amalgamation_Generator(
- [s for s in build_config.internal_headers
- if s.find('asm_macr_') == -1])
- botan_cpp = open('%s.cpp' % (amalg_basename), 'w')
+class AmalgamationGenerator(object):
+ filename_prefix = 'botan_all'
- botan_cpp.write(amalg_header)
+ _header_guard_pattern = re.compile('^#define BOTAN_.*_H_$')
- botan_cpp.write('\n#include "%s"\n' % (header_name))
+ @staticmethod
+ def strip_header_goop(header_name, header_lines):
+ lines = copy.copy(header_lines) # defensive copy
- botan_cpp.write(internal_header_amalag.header_includes)
- botan_cpp.write(internal_header_amalag.contents)
+ start_header_guard_index = None
+ for index, line in enumerate(lines):
+ if AmalgamationGenerator._header_guard_pattern.match(line):
+ start_header_guard_index = index
+ break
+ if start_header_guard_index is None:
+ raise InternalError("No header guard start found in " + header_name)
+
+ end_header_guard_index = None
+ for index, line in enumerate(lines):
+ if line == '#endif\n':
+ end_header_guard_index = index # override with last found
+ if end_header_guard_index is None:
+ raise InternalError("No header guard end found in " + header_name)
+
+ lines = lines[start_header_guard_index+1 : end_header_guard_index]
+
+ # Strip leading and trailing empty lines
+ while lines[0].strip() == "":
+ lines = lines[1:]
+ while lines[-1].strip() == "":
+ lines = lines[0:-1]
+
+ return lines
+
+ def __init__(self, build_paths, modules, options):
+ self._build_paths = build_paths
+ self._modules = modules
+ self._options = options
+
+ def _target_for_module(self, mod):
+ target = ''
+ if not self._options.single_amalgamation_file:
+ if mod.need_isa != []:
+ target = '_'.join(sorted(mod.need_isa))
+ if target == 'sse2' and self._options.arch == 'x86_64':
+ target = '' # SSE2 is always available on x86-64
+
+ if self._options.arch == 'x86_32' and 'simd' in mod.requires:
+ target = 'sse2'
+ return target
+
+ def _isas_for_target(self, target):
+ for mod in sorted(self._modules, key=lambda module: module.basename):
+ # Only first module for target is considered. Does this make sense?
+ if self._target_for_module(mod) == target:
+ out = set()
+ for isa in mod.need_isa:
+ if isa == 'aesni':
+ isa = "aes,ssse3,pclmul"
+ elif isa == 'rdrand':
+ isa = 'rdrnd'
+ out.add(isa)
+ return out
+ # Return set such that we can also iterate over result in the NA case
+ return set()
+
+ def _generate_headers(self):
+ pub_header_amalag = AmalgamationHeader(self._build_paths.public_headers)
+ header_name = '%s.h' % (AmalgamationGenerator.filename_prefix)
+ logging.info('Writing amalgamation header to %s' % (header_name))
+ pub_header_amalag.write_to_file(header_name, "BOTAN_AMALGAMATION_H_")
+
+ internal_headers = AmalgamationHeader(self._build_paths.internal_headers)
+ header_int_name = '%s_internal.h' % (AmalgamationGenerator.filename_prefix)
+ logging.info('Writing amalgamation header to %s' % (header_int_name))
+ internal_headers.write_to_file(header_int_name, "BOTAN_AMALGAMATION_INTERNAL_H_")
+
+ header_files = [header_name, header_int_name]
+ included_in_headers = pub_header_amalag.all_std_includes | internal_headers.all_std_includes
+ return header_files, included_in_headers
+
+ def _generate_sources(self, amalgamation_headers, included_in_headers): #pylint: disable=too-many-locals,too-many-branches
+ encoding_kwords = {}
+ if sys.version_info[0] == 3:
+ encoding_kwords['encoding'] = 'utf8'
+
+ # target to filepath map
+ amalgamation_sources = {}
+ for mod in self._modules:
+ target = self._target_for_module(mod)
+ amalgamation_sources[target] = '%s%s.cpp' % (
+ AmalgamationGenerator.filename_prefix,
+ '_' + target if target else '')
+
+ # file descriptors for all `amalgamation_sources`
+ amalgamation_files = {}
+ for target, filepath in amalgamation_sources.items():
+ logging.info('Writing amalgamation source to %s' % (filepath))
+ amalgamation_files[target] = open(filepath, 'w', **encoding_kwords)
+
+ for target, f in amalgamation_files.items():
+ AmalgamationHeader.write_banner(f)
+ f.write('\n')
+ for header in amalgamation_headers:
+ f.write('#include "%s"\n' % (header))
+ f.write('\n')
+
+ for isa in self._isas_for_target(target):
+
+ if isa == 'sse41':
+ isa = 'sse4.1'
+ elif isa == 'sse42':
+ isa = 'ssse4.2'
+
+ f.write('#if defined(__GNUG__) && !defined(__clang__)\n')
+ f.write('#pragma GCC target ("%s")\n' % (isa))
+ f.write('#endif\n')
+
+ # target to include header map
+ unconditional_headers_written = {}
+ for target, _ in amalgamation_sources.items():
+ unconditional_headers_written[target] = included_in_headers.copy()
+
+ for mod in sorted(self._modules, key=lambda module: module.basename):
+ tgt = self._target_for_module(mod)
+ for src in sorted(mod.source):
+ with open(src, 'r', **encoding_kwords) as f:
+ for line in f:
+ if AmalgamationHelper.is_botan_include(line):
+ # Botan headers are inlined in amalgamation headers
+ continue
- for src in build_config.sources:
- if src.endswith('.S'):
- continue
+ if AmalgamationHelper.is_any_include(line) in unconditional_headers_written[tgt]:
+ # This include (conditional or unconditional) was unconditionally added before
+ continue
+
+ amalgamation_files[tgt].write(line)
+ unconditional_header = AmalgamationHelper.is_unconditional_any_include(line)
+ if unconditional_header:
+ unconditional_headers_written[tgt].add(unconditional_header)
+
+ for f in amalgamation_files.values():
+ f.close()
+
+ return set(amalgamation_sources.values())
+
+ def generate(self):
+ amalgamation_headers, included_in_headers = self._generate_headers()
+ amalgamation_sources = self._generate_sources(amalgamation_headers, included_in_headers)
+ return (sorted(amalgamation_sources), sorted(amalgamation_headers))
- contents = open(src).readlines()
- for line in contents:
- if botan_include.search(line):
- continue
- else:
- botan_cpp.write(line)
-"""
-Test for the existence of a program
-"""
def have_program(program):
+ """
+ Test for the existence of a program
+ """
def exe_test(path, program):
exe_file = os.path.join(path, program)
@@ -1663,45 +2606,114 @@ def have_program(program):
if exe_test(path, program + suffix):
return True
+ logging.debug('Program %s not found' % (program))
return False
-"""
-Main driver
-"""
-def main(argv = None):
- if argv is None:
- argv = sys.argv
- logging.basicConfig(stream = sys.stdout,
- format = '%(levelname) 7s: %(message)s')
+class BotanConfigureLogHandler(logging.StreamHandler, object):
+ def emit(self, record):
+ # Do the default stuff first
+ super(BotanConfigureLogHandler, self).emit(record)
+ # Exit script if and ERROR or worse occurred
+ if record.levelno >= logging.ERROR:
+ sys.exit(1)
- options = process_command_line(argv[1:])
- def log_level():
- if options.verbose:
- return logging.DEBUG
- if options.quiet:
- return logging.WARNING
- return logging.INFO
+def setup_logging(options):
+ if options.verbose:
+ log_level = logging.DEBUG
+ elif options.quiet:
+ log_level = logging.WARNING
+ else:
+ log_level = logging.INFO
+
+ lh = BotanConfigureLogHandler(sys.stdout)
+ lh.setFormatter(logging.Formatter('%(levelname) 7s: %(message)s'))
+ logging.getLogger().addHandler(lh)
+ logging.getLogger().setLevel(log_level)
- logging.getLogger().setLevel(log_level())
- logging.debug('%s invoked with options "%s"' % (
- argv[0], ' '.join(argv[1:])))
+def load_info_files(search_dir, descr, filename_matcher, class_t):
+ info = {}
- logging.debug('Platform: OS="%s" machine="%s" proc="%s"' % (
- platform.system(), platform.machine(), platform.processor()))
+ def filename_matches(filename):
+ if isinstance(filename_matcher, str):
+ return filename == filename_matcher
+ else:
+ return filename_matcher.match(filename) is not None
+
+ for (dirpath, _, filenames) in os.walk(search_dir):
+ for filename in filenames:
+ filepath = os.path.join(dirpath, filename)
+ if filename_matches(filename):
+ info_obj = class_t(filepath)
+ info[info_obj.basename] = info_obj
+
+ if info:
+ infotxt_basenames = ' '.join(sorted([key for key in info]))
+ logging.debug('Loaded %d %s files: %s' % (len(info), descr, infotxt_basenames))
+ else:
+ logging.warning('Failed to load any %s files' % (descr))
+
+ return info
- if options.os == "java":
- raise Exception("Jython detected: need --os and --cpu to set target")
- options.base_dir = os.path.dirname(argv[0])
- options.src_dir = os.path.join(options.base_dir, 'src')
+def load_build_data_info_files(source_paths, descr, subdir, class_t):
+ matcher = re.compile(r'[_a-z0-9]+\.txt$')
+ return load_info_files(os.path.join(source_paths.build_data_dir, subdir), descr, matcher, class_t)
+
+
+# Workaround for Windows systems where antivirus is enabled GH #353
+def robust_rmtree(path, max_retries=5):
+ for _ in range(max_retries):
+ try:
+ shutil.rmtree(path)
+ return
+ except OSError:
+ time.sleep(0.1)
- options.build_data = os.path.join(options.src_dir, 'build-data')
- options.makefile_dir = os.path.join(options.build_data, 'makefile')
+ # Final attempt, pass any exceptions up to caller.
+ shutil.rmtree(path)
- (modules, archinfo, ccinfo, osinfo) = load_info_files(options)
+
+# Workaround for Windows systems where antivirus is enabled GH #353
+def robust_makedirs(directory, max_retries=5):
+ for _ in range(max_retries):
+ try:
+ os.makedirs(directory)
+ return
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ raise
+ else:
+ time.sleep(0.1)
+
+ # Final attempt, pass any exceptions up to caller.
+ os.makedirs(directory)
+
+
+# This is for otions that have --with-XYZ and --without-XYZ. If user does not
+# set any of those, we choose a default here.
+# Mutates `options`
+def set_defaults_for_unset_options(options, info_arch, info_cc): # pylint: disable=too-many-branches
+ if options.os is None:
+ system_from_python = platform.system().lower()
+ if re.match('^cygwin_.*', system_from_python):
+ logging.debug("Converting '%s' to 'cygwin'", system_from_python)
+ options.os = 'cygwin'
+ else:
+ options.os = system_from_python
+ logging.info('Guessing target OS is %s (use --os to set)' % (options.os))
+
+ def deduce_compiler_type_from_cc_bin(cc_bin):
+ if cc_bin.find('clang') != -1 or cc_bin in ['emcc', 'em++']:
+ return 'clang'
+ if cc_bin.find('-g++') != -1:
+ return 'gcc'
+ return None
+
+ if options.compiler is None and options.compiler_binary != None:
+ options.compiler = deduce_compiler_type_from_cc_bin(options.compiler_binary)
if options.compiler is None:
if options.os == 'windows':
@@ -1709,173 +2721,455 @@ def main(argv = None):
options.compiler = 'gcc'
else:
options.compiler = 'msvc'
+ elif options.os in ['darwin', 'freebsd', 'openbsd', 'ios']:
+ # Prefer Clang on these systems
+ if have_program('clang++'):
+ options.compiler = 'clang'
+ else:
+ options.compiler = 'gcc'
+ if options.os == 'openbsd':
+ # The assembler shipping with OpenBSD 5.9 does not support avx2
+ del info_cc['gcc'].isa_flags['avx2']
else:
options.compiler = 'gcc'
- logging.info('Guessing to use compiler %s (use --cc to set)' % (
- options.compiler))
- if options.os is None:
- options.os = platform.system().lower()
+ if options.compiler is None:
+ logging.error('Could not guess which compiler to use, use --cc or CXX to set')
+ else:
+ logging.info('Guessing to use compiler %s (use --cc or CXX to set)' % (options.compiler))
- if re.match('^cygwin_.*', options.os):
- logging.debug("Converting '%s' to 'cygwin'", options.os)
- options.os = 'cygwin'
+ if options.cpu is None:
+ (arch, cpu) = guess_processor(info_arch)
+ options.arch = arch
+ options.cpu = cpu
+ logging.info('Guessing target processor is a %s (use --cpu to set)' % (options.arch))
+
+ if options.with_documentation is True:
+ if options.with_sphinx is None and have_program('sphinx-build'):
+ logging.info('Found sphinx-build (use --without-sphinx to disable)')
+ options.with_sphinx = True
+ if options.with_rst2man is None and have_program('rst2man'):
+ logging.info('Found rst2man (use --without-rst2man to disable)')
+ options.with_rst2man = True
+
+
+# Mutates `options`
+def canonicalize_options(options, info_os, info_arch):
+ # pylint: disable=too-many-branches
+ if options.os not in info_os:
+ def find_canonical_os_name(os_name_variant):
+ for (canonical_os_name, os_info) in info_os.items():
+ if os_info.matches_name(os_name_variant):
+ return canonical_os_name
+ return os_name_variant # not found
+ options.os = find_canonical_os_name(options.os)
- if options.os == 'windows' and options.compiler == 'gcc':
- logging.warning('Detected GCC on Windows; use --os=cygwin or --os=mingw?')
+ # canonical ARCH/CPU
+ options.arch = canon_processor(info_arch, options.cpu)
+ if options.arch is None:
+ raise UserError('Unknown or unidentifiable processor "%s"' % (options.cpu))
- logging.info('Guessing target OS is %s (use --os to set)' % (options.os))
+ if options.cpu != options.arch:
+ logging.info('Canonicalized CPU target %s to %s', options.cpu, options.arch)
- if options.compiler not in ccinfo:
- raise Exception('Unknown compiler "%s"; available options: %s' % (
- options.compiler, ' '.join(sorted(ccinfo.keys()))))
+ shared_libs_supported = options.os in info_os and info_os[options.os].building_shared_supported()
- if options.os not in osinfo:
+ if not shared_libs_supported:
+ if options.build_shared_lib is not None:
+ logging.warning('Shared libs not supported on %s, disabling shared lib support' % (options.os))
+ options.build_shared_lib = False
+ else:
+ logging.info('Shared libs not supported on %s, disabling shared lib support' % (options.os))
- def find_canonical_os_name(os):
- for (name, info) in osinfo.items():
- if os in info.aliases:
- return name
- return os # not found
+ if options.os == 'windows' and options.build_shared_lib is None and options.build_static_lib is None:
+ options.build_shared_lib = True
- options.os = find_canonical_os_name(options.os)
+ if options.with_stack_protector is None:
+ if options.os in info_os:
+ options.with_stack_protector = info_os[options.os].use_stack_protector
- if options.os not in osinfo:
- raise Exception('Unknown OS "%s"; available options: %s' % (
- options.os, ' '.join(sorted(osinfo.keys()))))
+ if options.build_shared_lib is None:
+ if options.os == 'windows' and options.build_static_lib:
+ pass
+ else:
+ options.build_shared_lib = shared_libs_supported
- if options.cpu is None:
- (options.arch, options.cpu) = guess_processor(archinfo)
- logging.info('Guessing target processor is a %s/%s (use --cpu to set)' % (
- options.arch, options.cpu))
- else:
- cpu_from_user = options.cpu
- (options.arch, options.cpu) = canon_processor(archinfo, options.cpu)
- logging.info('Canonicalizized --cpu=%s to %s/%s' % (
- cpu_from_user, options.arch, options.cpu))
+ if options.build_static_lib is None:
+ if options.os == 'windows' and options.build_shared_lib:
+ pass
+ else:
+ options.build_static_lib = True
+
+ # Set default fuzzing lib
+ if options.build_fuzzers == 'libfuzzer' and options.fuzzer_lib is None:
+ options.fuzzer_lib = 'Fuzzer'
+
+# Checks user options for consistency
+# This method DOES NOT change options on behalf of the user but explains
+# why the given configuration does not work.
+def validate_options(options, info_os, info_cc, available_module_policies):
+ # pylint: disable=too-many-branches
+
+ if options.single_amalgamation_file and not options.amalgamation:
+ raise UserError("--single-amalgamation-file requires --amalgamation.")
+
+ if options.os == "java":
+ raise UserError("Jython detected: need --os and --cpu to set target")
+
+ if options.os not in info_os:
+ raise UserError('Unknown OS "%s"; available options: %s' % (
+ options.os, ' '.join(sorted(info_os.keys()))))
+
+ if options.compiler not in info_cc:
+ raise UserError('Unknown compiler "%s"; available options: %s' % (
+ options.compiler, ' '.join(sorted(info_cc.keys()))))
+
+ if options.cc_min_version is not None and not re.match(r'^[0-9]+\.[0-9]+$', options.cc_min_version):
+ raise UserError("--cc-min-version must have the format MAJOR.MINOR")
+
+ if options.module_policy and options.module_policy not in available_module_policies:
+ raise UserError("Unknown module set %s" % options.module_policy)
+
+ if options.os == 'llvm' or options.cpu == 'llvm':
+ if options.compiler != 'clang':
+ raise UserError('LLVM target requires using Clang')
+ if options.os != options.cpu:
+ raise UserError('LLVM target requires both CPU and OS be set to llvm')
+
+ if options.build_fuzzers != None:
+ if options.build_fuzzers not in ['libfuzzer', 'afl', 'klee', 'test']:
+ raise UserError('Bad value to --build-fuzzers')
+
+ if options.build_fuzzers == 'klee' and options.os != 'llvm':
+ raise UserError('Building for KLEE requires targeting LLVM')
+
+ if options.build_static_lib is False and options.build_shared_lib is False:
+ raise UserError('With both --disable-static-library and --disable-shared-library, nothing to do')
+
+ if options.os == 'windows' and options.build_static_lib is True and options.build_shared_lib is True:
+ raise UserError('On Windows only one of static lib and DLL can be selected')
+
+ if options.with_documentation is False:
+ if options.with_doxygen:
+ raise UserError('Using --with-doxygen plus --without-documentation makes no sense')
+ if options.with_sphinx:
+ raise UserError('Using --with-sphinx plus --without-documentation makes no sense')
+ if options.with_pdf:
+ raise UserError('Using --with-pdf plus --without-documentation makes no sense')
+
+ if options.with_pdf and not options.with_sphinx:
+ raise UserError('Option --with-pdf requires --with-sphinx')
+
+ if options.with_bakefile:
+ if options.os != 'windows' or options.compiler != 'msvc' or options.build_shared_lib is False:
+ raise UserError("Building via bakefile is only supported for MSVC DLL build")
+
+ if options.arch not in ['x86_64', 'x86_32']:
+ raise UserError("Bakefile only supports x86 targets")
+
+ # Warnings
+ if options.os == 'windows' and options.compiler != 'msvc':
+ logging.warning('The windows target is oriented towards MSVC; maybe you want --os=cygwin or --os=mingw')
+
+ if options.msvc_runtime:
+ if options.compiler != 'msvc':
+ raise UserError("Makes no sense to specify MSVC runtime for %s" % (options.compiler))
+
+ if options.msvc_runtime not in ['MT', 'MD', 'MTd', 'MDd']:
+ logging.warning("MSVC runtime option '%s' not known", (options.msvc_runtime))
+
+def run_compiler_preproc(options, ccinfo, source_file, default_return, extra_flags=None):
+ if extra_flags is None:
+ extra_flags = []
- logging.info('Target is %s-%s-%s-%s' % (
- options.compiler, options.os, options.arch, options.cpu))
+ cc_bin = options.compiler_binary or ccinfo.binary_name
- cc = ccinfo[options.compiler]
+ cmd = cc_bin.split(' ') + ccinfo.preproc_flags.split(' ') + extra_flags + [source_file]
- # Kind of a hack...
- options.extra_flags = ''
- if options.compiler == 'gcc':
+ try:
+ logging.debug("Running '%s'", ' '.join(cmd))
+ stdout, _ = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True).communicate()
+ cc_output = stdout
+ except OSError as e:
+ logging.warning('Could not execute %s: %s' % (cmd, e))
+ return default_return
+
+ def cleanup_output(output):
+ return ('\n'.join([l for l in output.splitlines() if l.startswith('#') is False])).strip()
+
+ return cleanup_output(cc_output)
+
+def calculate_cc_min_version(options, ccinfo, source_paths):
+ version_patterns = {
+ 'msvc': r'^ *MSVC ([0-9]{2})([0-9]{2})$',
+ 'gcc': r'^ *GCC ([0-9]+) ([0-9]+)$',
+ 'clang': r'^ *CLANG ([0-9]+) ([0-9]+)$',
+ 'xlc': r'^ *XLC (0x[0-9a-fA-F]{2})([0-9a-fA-F]{2})$'
+ }
+
+ if ccinfo.basename not in version_patterns:
+ logging.info("No compiler version detection available for %s" % (ccinfo.basename))
+ return "0.0"
+
+ detect_version_source = os.path.join(source_paths.build_data_dir, "detect_version.cpp")
+
+ cc_output = run_compiler_preproc(options, ccinfo, detect_version_source, "0.0")
+
+ match = re.search(version_patterns[ccinfo.basename], cc_output, flags=re.MULTILINE)
+ if match is None:
+ logging.warning("Tried to get %s version, but output '%s' does not match expected version format" % (
+ ccinfo.basename, cc_output))
+ return "0.0"
- def get_gcc_version(gcc_bin):
+ major_version = int(match.group(1), 0)
+ minor_version = int(match.group(2), 0)
+ cc_version = "%d.%d" % (major_version, minor_version)
+ logging.info('Auto-detected compiler version %s' % (cc_version))
+
+ if ccinfo.basename == 'msvc':
+ if major_version == 18:
+ logging.warning('MSVC 2013 support is deprecated and will be removed in a future release')
+ return cc_version
+
+def check_compiler_arch(options, ccinfo, archinfo, source_paths):
+ detect_version_source = os.path.join(source_paths.build_data_dir, 'detect_arch.cpp')
+
+ abi_flags = ccinfo.mach_abi_link_flags(options).split(' ')
+ cc_output = run_compiler_preproc(options, ccinfo, detect_version_source, 'UNKNOWN', abi_flags).lower()
+
+ if cc_output in ['', 'unknown']:
+ logging.warning('Unable to detect target architecture via compiler macro checks')
+ return None
+
+ if cc_output not in archinfo:
+ # Should not happen
+ logging.warning("Error detecting compiler target arch: '%s'", cc_output)
+ return None
+
+ logging.info('Auto-detected compiler arch %s' % (cc_output))
+ return cc_output
+
+def do_io_for_build(cc, arch, osinfo, using_mods, build_paths, source_paths, template_vars, options):
+ # pylint: disable=too-many-locals,too-many-branches
+
+ try:
+ robust_rmtree(build_paths.build_dir)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ logging.error('Problem while removing build dir: %s' % (e))
+
+ for build_dir in build_paths.build_dirs():
+ try:
+ robust_makedirs(build_dir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ logging.error('Error while creating "%s": %s' % (build_dir, e))
+
+ def write_template(sink, template):
+ with open(sink, 'w') as f:
+ f.write(process_template(template, template_vars))
+
+ def in_build_dir(p):
+ return os.path.join(build_paths.build_dir, p)
+ def in_build_data(p):
+ return os.path.join(source_paths.build_data_dir, p)
+
+ write_template(in_build_dir('build.h'), in_build_data('buildh.in'))
+ write_template(in_build_dir('botan.doxy'), in_build_data('botan.doxy.in'))
+
+ if 'botan_pkgconfig' in template_vars:
+ write_template(template_vars['botan_pkgconfig'], in_build_data('botan.pc.in'))
+
+ if options.os == 'windows':
+ write_template(in_build_dir('botan.iss'), in_build_data('innosetup.in'))
+
+ link_method = choose_link_method(options)
+
+ def link_headers(headers, visibility, directory):
+ logging.debug('Linking %d %s header files in %s' % (len(headers), visibility, directory))
+
+ for header_file in headers:
try:
- gcc_proc = subprocess.Popen(
- gcc_bin.split(' ') + ['-dumpversion'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True)
+ portable_symlink(header_file, directory, link_method)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise UserError('Error linking %s into %s: %s' % (header_file, directory, e))
- (stdout, stderr) = gcc_proc.communicate()
+ link_headers(build_paths.public_headers, 'public',
+ build_paths.botan_include_dir)
- if gcc_proc.returncode != 0:
- logging.warning("GCC returned non-zero result %s" % (stderr))
- return None
+ link_headers(build_paths.internal_headers, 'internal',
+ build_paths.internal_include_dir)
- gcc_version = stdout.strip()
+ link_headers(build_paths.external_headers, 'external',
+ build_paths.external_include_dir)
- logging.info('Detected gcc version %s' % (gcc_version))
- return gcc_version
- except OSError:
- logging.warning('Could not execute %s for version check' % (gcc_bin))
- return None
+ if options.amalgamation:
+ (amalg_cpp_files, amalg_headers) = AmalgamationGenerator(build_paths, using_mods, options).generate()
+ build_paths.lib_sources = amalg_cpp_files
+ template_vars['generated_files'] = ' '.join(amalg_cpp_files + amalg_headers)
- def is_64bit_arch(arch):
- if arch.endswith('64') or arch in ['alpha', 's390x']:
- return True
- return False
+ template_vars.update(generate_build_info(build_paths, using_mods, cc, arch, osinfo, options))
- gcc_version = get_gcc_version(options.compiler_binary or cc.binary_name)
+ with open(os.path.join(build_paths.build_dir, 'build_config.json'), 'w') as f:
+ json.dump(template_vars, f, sort_keys=True, indent=2)
+
+ if options.with_cmake:
+ logging.warning("CMake build is only for development: use make for production builds")
+ cmake_template = os.path.join(source_paths.build_data_dir, 'cmake.in')
+ write_template('CMakeLists.txt', cmake_template)
+ elif options.with_bakefile:
+ logging.warning("Bakefile build is only for development: use make for production builds")
+ bakefile_template = os.path.join(source_paths.build_data_dir, 'bakefile.in')
+ write_template('botan.bkl', bakefile_template)
+ else:
+ makefile_template = os.path.join(source_paths.build_data_dir, 'makefile.in')
+ write_template(template_vars['makefile_path'], makefile_template)
- if gcc_version:
+ if options.with_rst2man:
+ rst2man_file = os.path.join(build_paths.build_dir, 'botan.rst')
+ cli_doc = os.path.join(source_paths.doc_dir, 'manual/cli.rst')
- if not is_64bit_arch(options.arch) and not options.dumb_gcc:
- matching_version = '(4\.[01234]\.)|(3\.[34]\.)|(2\.95\.[0-4])'
+ cli_doc_contents = open(cli_doc).readlines()
- if re.search(matching_version, gcc_version):
- options.dumb_gcc = True
+ while cli_doc_contents[0] != "\n":
+ cli_doc_contents.pop(0)
- versions_without_tr1 = '(4\.0\.)|(3\.[0-4]\.)|(2\.95\.[0-4])'
+ rst2man_header = """
+botan
+=============================
- if options.with_tr1 == None and \
- re.search(versions_without_tr1, gcc_version):
- logging.info('Disabling TR1 support for this gcc, too old')
- options.with_tr1 = 'none'
+:Subtitle: Botan command line util
+:Manual section: 1
- versions_without_visibility = '(3\.[0-4]\.)|(2\.95\.[0-4])'
- if options.with_visibility == None and \
- re.search(versions_without_visibility, gcc_version):
- logging.info('Disabling DSO visibility support for this gcc, too old')
- options.with_visibility = False
+ """.strip()
- if options.dumb_gcc is True:
- logging.info('Setting -fpermissive to work around gcc bug')
- options.extra_flags = ' -fpermissive'
+ with open(rst2man_file, 'w') as f:
+ f.write(rst2man_header)
+ f.write("\n")
+ for line in cli_doc_contents:
+ f.write(line)
- if options.with_visibility is None:
- options.with_visibility = True
+ logging.info('Botan %s (revision %s) (%s %s) build setup is complete' % (
+ Version.as_string(),
+ Version.vc_rev(),
+ Version.release_type(),
+ ('dated %d' % (Version.datestamp())) if Version.datestamp() != 0 else 'undated'))
- if options.with_tr1 == None:
- if cc.has_tr1:
- logging.info('Assuming %s has TR1 (use --with-tr1=none to disable)' % (
- options.compiler))
- options.with_tr1 = 'system'
+ if options.unsafe_fuzzer_mode:
+ logging.warning("The fuzzer mode flag is labeled unsafe for a reason, this version is for testing only")
+
+def list_os_features(all_os_features, info_os):
+ for feat in all_os_features:
+ os_with_feat = [o for o in info_os.keys() if feat in info_os[o].target_features]
+ os_without_feat = [o for o in info_os.keys() if feat not in info_os[o].target_features]
+
+ if len(os_with_feat) < len(os_without_feat):
+ print("%s: %s" % (feat, ' '.join(sorted(os_with_feat))))
else:
- options.with_tr1 = 'none'
+ print("%s: %s" % (feat, '!' + ' !'.join(sorted(os_without_feat))))
+ return 0
- if options.with_sphinx is None:
- if have_program('sphinx-build'):
- logging.info('Found sphinx-build, will use it ' +
- '(use --without-sphinx to disable)')
- options.with_sphinx = True
- if options.via_amalgamation:
- options.gen_amalgamation = True
+def main(argv):
+ """
+ Main driver
+ """
+
+ # pylint: disable=too-many-locals
- if options.gen_amalgamation:
- if options.asm_ok:
- logging.info('Disabling assembly code, cannot use in amalgamation')
- options.asm_ok = False
+ options = process_command_line(argv[1:])
- modules_to_use = choose_modules_to_use(modules,
- archinfo[options.arch],
- options)
+ setup_logging(options)
- if not osinfo[options.os].build_shared:
- if options.build_shared_lib:
- logging.info('Disabling shared lib on %s' % (options.os))
- options.build_shared_lib = False
+ source_paths = SourcePaths(os.path.dirname(argv[0]))
+
+ info_modules = load_info_files(source_paths.lib_dir, 'Modules', "info.txt", ModuleInfo)
+
+ if options.list_modules:
+ for mod in sorted(info_modules.keys()):
+ print(mod)
+ return 0
+
+ info_arch = load_build_data_info_files(source_paths, 'CPU info', 'arch', ArchInfo)
+ info_os = load_build_data_info_files(source_paths, 'OS info', 'os', OsInfo)
+ info_cc = load_build_data_info_files(source_paths, 'compiler info', 'cc', CompilerInfo)
+ info_module_policies = load_build_data_info_files(source_paths, 'module policy', 'policy', ModulePolicyInfo)
+
+ all_os_features = sorted(set(flatten([o.target_features for o in info_os.values()])))
- build_config = BuildConfigurationInformation(options, modules_to_use)
- build_config.public_headers.append(
- os.path.join(build_config.build_dir, 'build.h'))
+ if options.list_os_features:
+ return list_os_features(all_os_features, info_os)
- template_vars = create_template_vars(build_config, options,
- modules_to_use,
- cc,
- archinfo[options.arch],
- osinfo[options.os])
+ for mod in info_modules.values():
+ mod.cross_check(info_arch, info_cc, all_os_features)
- # Performs the I/O
- setup_build(build_config, options, template_vars)
+ for policy in info_module_policies.values():
+ policy.cross_check(info_modules)
- if options.gen_amalgamation:
- generate_amalgamation(build_config)
+ logging.info('%s invoked with options "%s"', argv[0], ' '.join(argv[1:]))
- logging.info('Botan %s build setup is complete' % (
- build_config.version_string))
+ logging.info('Autodetected platform information: OS="%s" machine="%s" proc="%s"',
+ platform.system(), platform.machine(), platform.processor())
+
+ logging.debug('Known CPU names: ' + ' '.join(
+ sorted(flatten([[ainfo.basename] + ainfo.aliases for ainfo in info_arch.values()]))))
+
+ set_defaults_for_unset_options(options, info_arch, info_cc)
+ canonicalize_options(options, info_os, info_arch)
+ validate_options(options, info_os, info_cc, info_module_policies)
+
+ cc = info_cc[options.compiler]
+ arch = info_arch[options.arch]
+ osinfo = info_os[options.os]
+ module_policy = info_module_policies[options.module_policy] if options.module_policy else None
+
+ if options.enable_cc_tests:
+ cc_min_version = options.cc_min_version or calculate_cc_min_version(options, cc, source_paths)
+ cc_arch = check_compiler_arch(options, cc, info_arch, source_paths)
+
+ if cc_arch is not None and cc_arch != options.arch:
+ logging.warning("Configured target is %s but compiler probe indicates %s", options.arch, cc_arch)
+ else:
+ cc_min_version = options.cc_min_version or "0.0"
+
+ logging.info('Target is %s:%s-%s-%s' % (
+ options.compiler, cc_min_version, options.os, options.arch))
+
+ chooser = ModulesChooser(info_modules, module_policy, arch, osinfo, cc, cc_min_version, options)
+ loaded_module_names = chooser.choose()
+ using_mods = [info_modules[modname] for modname in loaded_module_names]
+
+ build_paths = BuildPaths(source_paths, options, using_mods)
+ build_paths.public_headers.append(os.path.join(build_paths.build_dir, 'build.h'))
+
+ template_vars = create_template_vars(source_paths, build_paths, options, using_mods, cc, arch, osinfo)
+
+ # Now we start writing to disk
+ do_io_for_build(cc, arch, osinfo, using_mods, build_paths, source_paths, template_vars, options)
+
+ return 0
if __name__ == '__main__':
try:
- main()
- except Exception as e:
- logging.error(str(e))
- #import traceback
- #traceback.print_exc(file=sys.stderr)
- sys.exit(1)
+ sys.exit(main(argv=sys.argv))
+ except UserError as e:
+ logging.debug(traceback.format_exc())
+ logging.error(e)
+ except Exception as e: # pylint: disable=broad-except
+ # error() will stop script, so wrap all information into one call
+ logging.error("""%s
+An internal error occurred.
+
+Don't panic, this is probably not your fault!
+
+Please report the entire output at https://github.com/randombit/botan or email
+to the mailing list https://lists.randombit.net/mailman/listinfo/botan-devel
+
+You'll meet friendly people happy to help!""" % traceback.format_exc())
+
sys.exit(0)