diff --git a/scripts/configure_source_files.py b/scripts/configure_source_files.py new file mode 100755 index 000000000..33ff21205 --- /dev/null +++ b/scripts/configure_source_files.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +"""Fill in files with the version information extracted from CMakeLists.txt. + +Currently only supports include/tf_psa_crypto/version.h. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import re +from typing import Dict, Pattern + + +class Configurator: + """Populate template files in a format that's a subset of CMake's configure_file.""" + + @staticmethod + def _find_matching_line(filename: str, regex: Pattern) -> bytes: + for line in open(filename, 'rb'): + m = regex.match(line) + if m: + return m.group(1) + raise Exception('No line matching {} found in {}' + .format(regex, filename)) + + def __init__(self, cmakelists: str, + project_name: str, + variable_prefix: str) -> None: + """Read information from the given CMakeLists.txt file.""" + set_version_re = re.compile(rb'\s*set\s*\(' + + re.escape(project_name.encode()) + + rb'_VERSION\s+(.*?)\s*\)') + version_string = self._find_matching_line(cmakelists, set_version_re) + numbers_re = re.compile(rb'([0-9]+)\.([0-9]+)\.([0-9]+)') + m = numbers_re.match(version_string) + if not m: + raise Exception('Version string "{}" does not have the expected format' + .format(version_string.decode())) + self.variables = {} #type: Dict[bytes, bytes] + prefix = variable_prefix.encode() + b'_VERSION_' + for suffix, value in zip((b'MAJOR', b'MINOR', b'PATCH'), + m.groups()): + self.variables[prefix + suffix] = value + self.variable_re = re.compile(rb'@(' + + rb'|'.join(self.variables.keys()) + + rb')@') + + def process_file(self, source_file: str, target_file: str) -> None: + """Fill the given templated files.""" + with open(target_file, 'wb') as out: + for _num, line in enumerate(open(source_file, 'rb'), 1): + line = re.sub(self.variable_re, + lambda m: self.variables[m.group(1)], + line) + out.write(line) + + def run(self, source_root: str, target_root: str) -> None: + """Fill templated files under source_root. + + The output goes under the target_root directory. + """ + for path in [ + 'include/tf_psa_crypto/version.h', + ]: + self.process_file(source_root + '/' + path + '.in', + target_root + '/' + path) + + +def main() -> None: + """Process the command line and generate output files.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--cmakelists', '-c', + default='CMakeLists.txt', + help='CMakeLists.txt containing the version information') + parser.add_argument('--directory', '-d', + default=os.curdir, + help='Root directory of the output tree (default: current directory)') + parser.add_argument('--project-name', + default='TF_PSA_CRYPTO', + help='Project name in CMakeLists.txt') + parser.add_argument('--variable-prefix', + default='TF-PSA-Crypto', + help='Prefix for the variables containing version numbers') + parser.add_argument('--source', '-s', + default=os.curdir, + help='Root directory of the source tree (default: current directory)') + options = parser.parse_args() + configurator = Configurator(options.cmakelists, + options.project_name, options.variable_prefix) + configurator.run(options.source, options.directory) + +if __name__ == '__main__': + main() diff --git a/scripts/make_makefile.py b/scripts/make_makefile.py index b3a4c232b..0e6287804 100755 --- a/scripts/make_makefile.py +++ b/scripts/make_makefile.py @@ -92,6 +92,7 @@ class MakefileMaker: Typical usage: MakefileMaker(options, source_path).generate() """ + #pylint: disable=too-many-public-methods def __init__(self, options, source_path: str) -> None: """Initialize a makefile generator. @@ -118,6 +119,10 @@ def __init__(self, options, source_path: str) -> None: self.help = {} #type: Dict[str, str] # Directories containing targets self.target_directories = set() #type: Set[str] + # Generated header files: + # {path_in_include_directive: location_under_build} + # This must be populated before calling collect_c_dependencies(). + self.generated_header_files = {} #type: Dict[str, str] # Dependencies of C files ({c_or_h_file: {h_file, ...}}). Paths are # relative to the source or build directory. self.c_dependency_cache = {} #type: Dict[str, FrozenSet[str]] @@ -200,6 +205,10 @@ def source_file(self, path: Union[pathlib.Path, str]) -> SourceFile: """Construct a SourceFile object for the given path.""" return SourceFile(self.source_path, path) + def find_source_file(self, inner_path: str) -> str: + """Return the path to the given source file in make syntax.""" + return self.source_file(inner_path).make_path() + def iterate_source_files(self, *patterns: str) -> Iterator[SourceFile]: """List the source files matching any of the specified patterns. @@ -251,8 +260,6 @@ def collect_c_dependencies(self, c_file: str, might be defined: it bases its analysis solely on the textual presence of "#include". - Note that dependencies in the build tree are not supported yet. - This function uses a cache internally, so repeated calls with the same argument return almost instantly. @@ -273,10 +280,13 @@ def collect_c_dependencies(self, c_file: str, if m is None: continue filename = m.group(1) - for subdir in include_path: - if self.source_path.joinpath(subdir, filename).exists(): - dependencies.add('/'.join([subdir, filename])) - break + if filename in self.generated_header_files: + dependencies.add(filename) + else: + for subdir in include_path: + if self.source_path.joinpath(subdir, filename).exists(): + dependencies.add('/'.join([subdir, filename])) + break for dep in frozenset(dependencies): dependencies |= self.collect_c_dependencies(dep, stack) frozen = frozenset(dependencies) @@ -289,7 +299,10 @@ def targets_for_c(self, """Emit targets for a .c source file.""" dep_set = set(deps) for dep in self.collect_c_dependencies(src.relative_path()): - dep_set.add(self.source_file(dep).make_path()) + if dep in self.generated_header_files: + dep_set.add(self.generated_header_files[dep]) + else: + dep_set.add(self.find_source_file(dep)) for switch, extension in [ ('-c', '$(OBJ_EXT)',), ('-s', '$(ASM_EXT)',), @@ -323,6 +336,22 @@ def settings_section(self) -> None: self.blank_line() self.target('default', ['lib'], [], phony=True) + def version_h_subsection(self) -> None: + """Generate the target for .""" + location = 'include/tf_psa_crypto/version.h' + self.target(location, + ['$(SOURCE_DIR)/CMakeLists.txt', + '$(SOURCE_DIR)/include/tf_psa_crypto/version.h.in'], + [sjoin('$(PYTHON) scripts/configure_source_files.py', + '-c $(SOURCE_DIR)/CMakeLists.txt', + '-s $(SOURCE_DIR)')]) + self.generated_header_files['tf_psa_crypto/version.h'] = location + self.c_dependency_cache['tf_psa_crypto/version.h'] = frozenset() + + def generated_sources_section(self) -> None: + """Generate targets to build generated source files.""" + self.version_h_subsection() + def library_section(self) -> None: """Generate targets to build the library.""" c_files = self.list_source_files('core/*.c', 'drivers/builtin/src/*.c') @@ -363,6 +392,8 @@ def output_all(self) -> None: self.blank_line() self.settings_section() self.blank_line() + self.generated_sources_section() + self.blank_line() self.library_section() self.blank_line() self.clean_section()