Skip to content

Commit

Permalink
dash: Integrate DASH tool in Bazel build
Browse files Browse the repository at this point in the history
This commit adds support for the DASH tool within the S-CORE Bazel build system.
The integration includes:

- Defining custom Bazel rules to handle the DASH tool's setup and execution.
- Adding necessary dependencies in BUILD files to ensure compatibility with
  existing project requirements.
- Validating the integration with the current CI pipeline to maintain a seamless build
  and deployment process.
- Updating documentation (if applicable) to reflect the addition of DASH
  in the Bazel workflow.

This enhancement simplifies the workflow by consolidating tools into the Bazel build system,
improving build automation and traceability.

Issue-ref: see eclipse-score#126
  • Loading branch information
nradakovic committed Jan 13, 2025
1 parent 872caac commit d64c31a
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
test --test_output=errors

build --java_language_version=17
build --tool_java_language_version=17
build --java_runtime_version=remotejdk_17
build --tool_java_runtime_version=remotejdk_17
24 changes: 24 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,27 @@ bazel_dep(name = "buildifier_prebuilt", version = "7.3.1")
#
###############################################################################
bazel_dep(name = "aspect_rules_lint", version = "1.0.3")

###############################################################################
#
# Java version
#
###############################################################################
bazel_dep(name = "rules_java", version = "8.6.3")

###############################################################################
#
# HTTP Jar rule deps
#
###############################################################################
http_jar = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar")

DASH_VERSION = "1.1.0"

http_jar(
name = "dash_license_tool",
sha256 = "ba4e84e1981f0e51f92a42ec8fc8b9668dabb08d1279afde46b8414e11752b06",
urls = [
"https://repo.eclipse.org/content/repositories/dash-licenses/org/eclipse/dash/org.eclipse.dash.licenses/{version}/org.eclipse.dash.licenses-{version}.jar".format(version = DASH_VERSION),
],
)
9 changes: 9 additions & 0 deletions docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,12 @@ py_venv(
# Until release of esbonio 1.x, we need to install it ourselves so the VS Code extension can find it.
deps = sphinx_requirements + [requirement("esbonio")],
)

# Needed for Dash tool to check python dependency licenses.
filegroup(
name = "requirements_lock",
srcs = [
"_tooling/requirements_lock.txt",
],
visibility = ["//visibility:public"],
)
19 changes: 19 additions & 0 deletions tools/dash/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

load("//tools/dash:dash.bzl", "dash_license_checker")

dash_license_checker(
name = "check_python_licenses",
src = "//docs:requirements_lock",
)
20 changes: 20 additions & 0 deletions tools/dash/converters/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

py_binary(
name = "dash_format_converter",
srcs = [
"dash_format_converter.py",
],
visibility = ["//visibility:public"],
)
51 changes: 51 additions & 0 deletions tools/dash/converters/dash_format_converter.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

""" Bazel rule for generating dash formatted requirements file
"""

def _impl(ctx):
""" The implementation function of the rule.
"""

output = ctx.actions.declare_file("formatted.txt")
args = ctx.actions.args()
args.add("-i", ctx.file.requirement_file)
args.add("-o", output)

ctx.actions.run(
inputs = [ctx.file.requirement_file],
outputs = [output],
arguments = [args],
progress_message = "Generating Dash formatted dependency file ...",
mnemonic = "DashFormat",
executable = ctx.executable._tool,
)
return DefaultInfo(files = depset([output]))

dash_format_converter = rule(
implementation = _impl,
attrs = {
"requirement_file": attr.label(
mandatory = True,
allow_single_file = True,
doc = "The requirement (requirement_lock.txt) input file which holds deps",
),
"_tool": attr.label(
default = Label("//tools/dash/converters:dash_format_converter"),
executable = True,
cfg = "exec",
doc = "",
),
},
)
239 changes: 239 additions & 0 deletions tools/dash/converters/dash_format_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# *******************************************************************************
# Copyright (c) 2024 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
"""The tool for converting requirements.txt into dash checker format"""

import re
import sys
import argparse
import logging
from pathlib import Path
from typing import Optional

LOGGER = logging.getLogger()

COLORS = {
"BLUE": "\033[34m",
"GREEN": "\033[32m",
"YELLOW": "\033[33m",
"RED": "\033[31m",
"DARK_RED": "\033[35;1m",
"ENDC": "\033[0m",
}

LOGGER_COLORS = {
"DEBUG": COLORS["BLUE"],
"INFO": COLORS["GREEN"],
"WARNING": COLORS["YELLOW"],
"ERROR": COLORS["RED"],
"CRITICAL": COLORS["DARK_RED"],
}


class ColoredFormatter(logging.Formatter):
"""
A custom logging formatter to add color to log level names based on the logging level.
The `ColoredFormatter` class extends `logging.Formatter` and overrides the `format`
method to add color codes to the log level name (e.g., `INFO`, `WARNING`, `ERROR`)
based on a predefined color mapping in `LOGGER_COLORS`. This color coding helps in
visually distinguishing log messages by severity.
Attributes:
LOGGER_COLORS (dict): A dictionary mapping log level names (e.g., "INFO", "ERROR")
to their respective color codes.
COLORS (dict): A dictionary of terminal color codes, including an "ENDC" key to reset
colors after the level name.
Methods:
format(record): Adds color to the `levelname` attribute of the log record and then
formats the record as per the superclass `Formatter`.
"""

def format(self, record):
log_color = LOGGER_COLORS.get(record.levelname, "")
record.levelname = f"{log_color}{record.levelname}:{COLORS['ENDC']}"
return super().format(record)


def configure_logging(log_file_path: Path = None, verbose: bool = False) -> None:
"""
Configures the logging settings for the application.
Args:
log_file_path (Path, optional): The file path where logs should be written.
If not provided, logs are written to the console. Defaults to `None`.
verbose (bool, optional): A flag that determines the logging level.
If `True`, sets the logging level to DEBUG; if `False`, sets it to INFO.
Defaults to `False`.
Returns:
None: This function does not return any value, it only configures logging.
Notes:
- If `log_file_path` is provided, the log messages will be saved to the specified file.
- If `verbose` is `True`, detailed logs (DEBUG level) will be captured; otherwise,
less detailed logs (INFO level) will be captured.
- The default logging format is `%(asctime)s - %(name)s - %(levelname)s - %(message)s`.
"""
log_level = logging.DEBUG if verbose else logging.INFO
LOGGER.setLevel(log_level)
LOGGER.handlers.clear()

if log_file_path is not None:
handler = logging.FileHandler(log_file_path)
formatter = logging.Formatter("%(levelname)s: %(message)s")
else:
handler = logging.StreamHandler()
formatter = ColoredFormatter("%(levelname)s %(message)s")

handler.setLevel(log_level)
handler.setFormatter(formatter)
LOGGER.addHandler(handler)


def format_line(
line: str, regex: str = r"([a-zA-Z0-9_-]+)==([a-zA-Z0-9.\-_]+)"
) -> Optional[str]:
"""
Formats a line of text by matching a specified regex pattern and extracting components.
Args:
line (str): The input string to be processed.
regex (str, optional): A regular expression pattern to match the input line.
Returns:
Optional[str]: A formatted string based on the regex match if the pattern is found,
or None if no match is found.
Notes:
- The function uses the provided regex to capture two components from the input line:
the package name and its version.
- The formatted string follows the pattern "pypi/pypi/-/{package}/{version}".
- If the regex does not match, None is returned.
"""

ret = None
match = re.match(f"{regex}", line)

if match:
package, version = match.groups()
ret = f"pypi/pypi/-/{package}/{version}"

return ret


def convert_to_dash_format(input_file: Path, output_file: Path) -> int:
"""
Converts the content of an input to a "dash format" and writes output file.
The exact transformation applied to the content is assumed to replace specific patterns or
structures with a dash-separated format, although the details depend on the implementation.
Args:
input_file (Path): Path to the input file containing the original content.
output_file (Path): Path to the output file where the converted content will be written.
Returns:
int: Error thrown by system over exceptions.
"""
encoding = "utf-8"
with open(input_file, "r", encoding=encoding) as infile:
with open(output_file, "w", encoding=encoding) as outfile:
for line in infile:
formatted_line = format_line(line.strip())
if formatted_line:
outfile.write(formatted_line + "\n")


def parse_arguments(argv: list[str]) -> argparse.Namespace:
"""
Parses command-line arguments passed to the script.
Args:
argv (list[str]): A list of command-line arguments, typically `sys.argv[1:]`.
Returns:
argparse.Namespace: An object containing the parsed arguments as attributes.
Notes:
- This function expects an `argparse.ArgumentParser` to be configured with
the required arguments. If `argv` is not provided, it defaults to an empty list.
- Use the `argparse.Namespace` object to access parsed arguments by their names.
"""

parser = argparse.ArgumentParser(
description="The tool for converting requirements.txt into dash \
checker format."
)

parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable debug logging level",
)

parser.add_argument(
"-l",
"--log-file",
type=Path,
default=None,
help="Redirect logs from STDOUT to this file",
)

parser.add_argument(
"-i",
"--input",
type=Path,
required=True,
help="Path to the requirement.txt file",
)

parser.add_argument(
"-o",
"--output",
type=Path,
required=True,
help="Path to the formatted_list.txt file",
)

return parser.parse_args(argv)


def main(argv: list[str] = None) -> int:
"""
The main entry point of the script.
Args:
argv (list[str], optional): A list of command-line arguments. If not provided,
defaults to `None`, in which case `sys.argv[1:]` is typically used.
Returns:
int: An exit code where `0` indicates successful execution, and any non-zero
value indicates an error.
Notes:
- This function is often called in the `if __name__ == "__main__":` block.
- The function typically orchestrates parsing arguments, performing the core
logic of the script, and handling exceptions.
- Ensure the function catches and logs errors appropriately before returning
a non-zero exit code.
"""
args = parse_arguments(argv if argv is not None else sys.argv[1:])
configure_logging(args.log_file, args.verbose)
convert_to_dash_format(args.input, args.output)
return 0


if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Loading

0 comments on commit d64c31a

Please sign in to comment.