diff --git a/tools/cr_checker/cr_checker.bzl b/tools/cr_checker/cr_checker.bzl index b41ff3ce..edbf8e45 100644 --- a/tools/cr_checker/cr_checker.bzl +++ b/tools/cr_checker/cr_checker.bzl @@ -18,6 +18,7 @@ def copyright_checker( srcs, visibility, template = "//tools/cr_checker/resources:templates", + config = "//tools/cr_checker/resources:config", extensions = [], offset = 0, debug = False, @@ -34,6 +35,8 @@ def copyright_checker( targets can use this rule. template (str, optional): Path to the template resource used for validation. Defaults to "//tools/cr_checker/resources:templates". + config (str, optional): Path to the config resource used for project variables. + Defaults to "//tools/cr_checker/resources:config". extensions (list, optional): A list of file extensions to filter the source files. Defaults to an empty list, meaning all files are checked. offset (int, optional): The line offset for applying checks or modifications. @@ -53,7 +56,10 @@ def copyright_checker( "{}.fix".format(name), ] - args = ["-t $(location {})".format(template)] + args = [ + "-t $(location {})".format(template), + "-c $(location {})".format(config), + ] data = [] if len(extensions): args.append("-e {exts}".format( @@ -85,6 +91,7 @@ def copyright_checker( args = args, data = srcs + [ template, + config, ], visibility = visibility, ) diff --git a/tools/cr_checker/resources/BUILD b/tools/cr_checker/resources/BUILD index b4b9ed74..05abe700 100644 --- a/tools/cr_checker/resources/BUILD +++ b/tools/cr_checker/resources/BUILD @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) 2025 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -18,3 +18,11 @@ filegroup( ], visibility = ["//visibility:public"], ) + +filegroup( + name = "config", + srcs = [ + "config.json", + ], + visibility = ["//visibility:public"], +) diff --git a/tools/cr_checker/resources/config.json b/tools/cr_checker/resources/config.json new file mode 100644 index 00000000..addb26da --- /dev/null +++ b/tools/cr_checker/resources/config.json @@ -0,0 +1,3 @@ +{ + "years": [2024, 2025] +} diff --git a/tools/cr_checker/resources/templates.ini b/tools/cr_checker/resources/templates.ini index 01cf29cf..63c2946d 100644 --- a/tools/cr_checker/resources/templates.ini +++ b/tools/cr_checker/resources/templates.ini @@ -12,7 +12,7 @@ # ******************************************************************************* [cpp,c,h,hpp] /******************************************************************************** -* Copyright (c) 2024 Contributors to the Eclipse Foundation +* Copyright (c) {year} Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -25,7 +25,7 @@ ********************************************************************************/ [py,sh,bzl,ini,yml,BUILD,bazel] # ******************************************************************************* -# Copyright (c) 2024 Contributors to the Eclipse Foundation +# Copyright (c) {year} Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -39,7 +39,7 @@ [rst] .. # ******************************************************************************* - # Copyright (c) 2024 Contributors to the Eclipse Foundation + # Copyright (c) {year} Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. diff --git a/tools/cr_checker/tool/cr_checker.py b/tools/cr_checker/tool/cr_checker.py index a9603f62..74ca79a6 100755 --- a/tools/cr_checker/tool/cr_checker.py +++ b/tools/cr_checker/tool/cr_checker.py @@ -18,6 +18,8 @@ import sys import tempfile import mmap +import json +from datetime import datetime from pathlib import Path LOGGER = logging.getLogger() @@ -98,6 +100,21 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) +def get_years_from_config(config_path): + """ + Reads the years from a JSON configuration file. + + Args: + config_path (Path): Path to the configuration JSON file. + + Returns: + list: List of years from the configuration file. + """ + with config_path.open("r") as file: + config = json.load(file) + return config.get("years", []) + + def load_templates(path): """ Loads the copyright templates from a configuration file. @@ -228,7 +245,7 @@ def load_text_from_file_with_mmap(path, header_length, encoding, offset): return fmap[:header_length].decode(encoding) -def has_copyright(path, copyright_text, use_mmap, encoding, offset): +def has_copyright(path, copyright_text, use_mmap, encoding, offset, config): """ Checks if the specified copyright text is present in the beginning of a file. @@ -242,6 +259,8 @@ def has_copyright(path, copyright_text, use_mmap, encoding, offset): offset (int): Additional number of characters to read beyond the length of `copyright_text`, used to account for extra content (such as a shebang) before the copyright text. + config (Path): Path to the config JSON file where configuration + variables are stored (e.g. years for copyright headers). Returns: bool: True if the file contains the copyright text, False if it is missing. @@ -254,10 +273,14 @@ def has_copyright(path, copyright_text, use_mmap, encoding, offset): if use_mmap: load_text = load_text_from_file_with_mmap - if copyright_text not in load_text(path, len(copyright_text), encoding, offset): - return False - LOGGER.debug("File %s has copyright.", path) - return True + for year in get_years_from_config(config): + formated_cr = copyright_text.format(year=year) + if formated_cr in load_text(path, len(formated_cr), encoding, offset): + LOGGER.debug("File %s has copyright.", path) + return True + + LOGGER.debug("File %s doesn't have copyright.", path) + return False def get_files_from_dir(directory, exts=None): @@ -361,7 +384,7 @@ def fix_copyright(path, copyright_text, encoding, offset): with open(path, "w", encoding=encoding) as handle: if offset > 0: handle.write(first_line + "\n") - handle.write(copyright_text) + handle.write(copyright_text.format(year=datetime.now().year)) # Reset the file pointer to the beginning of the temporary file temp.seek(0) for chunk in iter(lambda: temp.read(4096), ""): @@ -369,7 +392,9 @@ def fix_copyright(path, copyright_text, encoding, offset): LOGGER.info("Fixed missing header in: %s", path) -def process_files(files, templates, fix, use_mmap=False, encoding="utf-8", offset=0): # pylint: disable=too-many-arguments +def process_files( + files, templates, fix, config, use_mmap=False, encoding="utf-8", offset=0 +): # pylint: disable=too-many-arguments """ Processes a list of files to check for the presence of copyright text. @@ -378,6 +403,8 @@ def process_files(files, templates, fix, use_mmap=False, encoding="utf-8", offse templates (dict): A dictionary where keys are file extensions (e.g., '.py', '.txt') and values are strings or patterns representing the required copyright text. + config (Path): Path to the config JSON file where configuration + variables are stored (e.g. years for copyright headers). use_mmap (bool): Flag for using mmap function for reading files (instead of standard option). encoding (str): Encoding type to use when reading the file. @@ -397,7 +424,7 @@ def process_files(files, templates, fix, use_mmap=False, encoding="utf-8", offse "Skipped (no configuration for selected file extension): %s", item ) continue - if not has_copyright(item, templates[key], use_mmap, encoding, offset): + if not has_copyright(item, templates[key], use_mmap, encoding, offset, config): if fix: fix_copyright(item, templates[key], encoding, offset) results["no_copyright"] += 1 @@ -433,6 +460,14 @@ def parse_arguments(argv): help="Path to the template file", ) + parser.add_argument( + "-c", + "--config-file", + type=Path, + required=True, + help="Path to the config file", + ) + parser.add_argument( "-v", "--verbose", action="store_true", help="Enable debug logging level" ) @@ -524,7 +559,13 @@ def main(argv=None): LOGGER.debug("Running check on files: %s", files) results = process_files( - files, templates, args.fix, args.use_memory_map, args.encoding, args.offset + files, + templates, + args.fix, + args.config_file, + args.use_memory_map, + args.encoding, + args.offset, ) total_no = results["no_copyright"] total_fixes = results["fixed"]