Skip to content

Commit

Permalink
fix: Support configurable year in cr_checker
Browse files Browse the repository at this point in the history
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 eclipse-score#161
  • Loading branch information
nradakovic committed Jan 16, 2025
1 parent 6991a3e commit 17f2016
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
9 changes: 8 additions & 1 deletion tools/cr_checker/cr_checker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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(
Expand Down Expand Up @@ -85,6 +91,7 @@ def copyright_checker(
args = args,
data = srcs + [
template,
config,
],
visibility = visibility,
)
10 changes: 9 additions & 1 deletion tools/cr_checker/resources/BUILD
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,3 +18,11 @@ filegroup(
],
visibility = ["//visibility:public"],
)

filegroup(
name = "config",
srcs = [
"config.json",
],
visibility = ["//visibility:public"],
)
3 changes: 3 additions & 0 deletions tools/cr_checker/resources/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"years": [2024, 2025]
}
6 changes: 3 additions & 3 deletions tools/cr_checker/resources/templates.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
59 changes: 50 additions & 9 deletions tools/cr_checker/tool/cr_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import sys
import tempfile
import mmap
import json
from datetime import datetime
from pathlib import Path

LOGGER = logging.getLogger()
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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):
Expand Down Expand Up @@ -361,15 +384,17 @@ 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), ""):
handle.write(chunk)
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.
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"]
Expand Down

0 comments on commit 17f2016

Please sign in to comment.