From 17f2016896331e534bdfb1f828ba49c59d689f90 Mon Sep 17 00:00:00 2001 From: Nikola Radakovic Date: Thu, 16 Jan 2025 11:01:51 +0000 Subject: [PATCH] fix: Support configurable year in cr_checker This PR introduces a configurable approach for setting the year in copyright header text. The cr_checker tool will validate all years specified in the config.json file during verification mode. Additionally, it will automatically update the copyright year to the current year when running in fix mode. resolves #161 --- tools/cr_checker/cr_checker.bzl | 9 +++- tools/cr_checker/resources/BUILD | 10 +++- tools/cr_checker/resources/config.json | 3 ++ tools/cr_checker/resources/templates.ini | 6 +-- tools/cr_checker/tool/cr_checker.py | 59 ++++++++++++++++++++---- 5 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 tools/cr_checker/resources/config.json 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"]