| #!/usr/bin/env python3 |
| # Copyright 2023 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import sys |
| from typing import Callable, List, Tuple, Union |
| |
| _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) |
| # The repo's root directory. |
| _ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..")) |
| |
| # Add the repo's root directory for clearer imports. |
| sys.path.insert(0, _ROOT_DIR) |
| |
| import gclient_utils |
| import metadata.parse |
| import metadata.validation_result as vr |
| |
| |
| _TRANSITION_PRESCRIPT = ( |
| "The following issue should be addressed now, as it will become a " |
| "presubmit error (instead of warning) once third party metadata " |
| "validation is enforced.\nThird party metadata issue:") |
| |
| |
| def validate_content(content: str, source_file_dir: str, |
| repo_root_dir: str) -> List[vr.ValidationResult]: |
| """Validate the content as a metadata file. |
| |
| Args: |
| content: the entire content of a file to be validated as a |
| metadata file. |
| source_file_dir: the directory of the metadata file that the |
| license file value is from; this is needed to |
| construct file paths to license files. |
| repo_root_dir: the repository's root directory; this is needed |
| to construct file paths to license files. |
| |
| Returns: the validation results for the given content. |
| """ |
| results = [] |
| dependencies = metadata.parse.parse_content(content) |
| if not dependencies: |
| result = vr.ValidationError(reason="No dependency metadata found.") |
| return [result] |
| |
| for dependency in dependencies: |
| dependency_results = dependency.validate( |
| source_file_dir=source_file_dir, repo_root_dir=repo_root_dir) |
| results.extend(dependency_results) |
| return results |
| |
| |
| def _construct_file_read_error(filepath: str, cause: str) -> vr.ValidationError: |
| """Helper function to create a validation error for a |
| file reading issue. |
| """ |
| result = vr.ValidationError( |
| reason="Cannot read metadata file.", |
| additional=[f"Attempted to read '{filepath}' but {cause}."]) |
| return result |
| |
| |
| def validate_file( |
| filepath: str, |
| repo_root_dir: str, |
| reader: Callable[[str], Union[str, bytes]] = None, |
| ) -> List[vr.ValidationResult]: |
| """Validate the item located at the given filepath is a valid |
| dependency metadata file. |
| |
| Args: |
| filepath: the path to validate, e.g. |
| "/chromium/src/third_party/libname/README.chromium" |
| repo_root_dir: the repository's root directory; this is needed |
| to construct file paths to license files. |
| reader (optional): callable function/method to read the content |
| of the file. |
| |
| Returns: the validation results for the given filepath and its |
| contents, if it exists. |
| """ |
| if reader is None: |
| reader = gclient_utils.FileRead |
| |
| try: |
| content = reader(filepath) |
| except FileNotFoundError: |
| return [_construct_file_read_error(filepath, "file not found")] |
| except PermissionError: |
| return [_construct_file_read_error(filepath, "access denied")] |
| except Exception as e: |
| return [ |
| _construct_file_read_error(filepath, f"unexpected error: '{e}'") |
| ] |
| else: |
| if not isinstance(content, str): |
| return [_construct_file_read_error(filepath, "decoding failed")] |
| |
| # Get the directory the metadata file is in. |
| source_file_dir = os.path.dirname(filepath) |
| return validate_content(content=content, |
| source_file_dir=source_file_dir, |
| repo_root_dir=repo_root_dir) |
| |
| |
| def check_file( |
| filepath: str, |
| repo_root_dir: str, |
| reader: Callable[[str], Union[str, bytes]] = None, |
| ) -> Tuple[List[str], List[str]]: |
| """Run metadata validation on the given filepath, and return all |
| validation errors and validation warnings. |
| |
| Args: |
| filepath: the path to a metadata file, e.g. |
| "/chromium/src/third_party/libname/README.chromium" |
| repo_root_dir: the repository's root directory; this is needed |
| to construct file paths to license files. |
| reader (optional): callable function/method to read the content |
| of the file. |
| |
| Returns: |
| error_messages: the fatal validation issues present in the file; |
| i.e. presubmit should fail. |
| warning_messages: the non-fatal validation issues present in the |
| file; i.e. presubmit should still pass. |
| """ |
| results = validate_file(filepath=filepath, |
| repo_root_dir=repo_root_dir, |
| reader=reader) |
| |
| error_messages = [] |
| warning_messages = [] |
| for result in results: |
| # TODO(aredulla): Actually distinguish between validation errors |
| # and warnings. The quality of metadata is currently being |
| # uplifted, but is not yet guaranteed to pass validation. So for |
| # now, all validation results will be returned as warnings so |
| # CLs are not blocked by invalid metadata in presubmits yet. |
| # Bug: b/285453019. |
| if result.is_fatal(): |
| message = result.get_message(prescript=_TRANSITION_PRESCRIPT, |
| width=60) |
| else: |
| message = result.get_message(width=60) |
| warning_messages.append(message) |
| |
| return error_messages, warning_messages |