You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
5.8 KiB
Python
157 lines
5.8 KiB
Python
#!/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,
|
|
is_open_source_project: bool = False) -> 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.
|
|
is_open_source_project: whether the project is open source.
|
|
|
|
Returns: the validation results for the given content, sorted based
|
|
severity then message.
|
|
"""
|
|
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,
|
|
is_open_source_project=is_open_source_project,
|
|
)
|
|
results.extend(dependency_results)
|
|
return sorted(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,
|
|
is_open_source_project: bool = False,
|
|
) -> 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.
|
|
is_open_source_project: whether to allow reciprocal licenses.
|
|
This should only be True for open source projects.
|
|
|
|
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,
|
|
is_open_source_project=is_open_source_project)
|
|
|
|
|
|
def check_file(
|
|
filepath: str,
|
|
repo_root_dir: str,
|
|
reader: Callable[[str], Union[str, bytes]] = None,
|
|
is_open_source_project: bool = False,
|
|
) -> 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.
|
|
is_open_source_project: whether to allow reciprocal licenses.
|
|
This should only be True for open source projects.
|
|
|
|
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,
|
|
is_open_source_project=is_open_source_project)
|
|
|
|
error_messages = []
|
|
warning_messages = []
|
|
for result in results:
|
|
target = error_messages if result.is_fatal() else warning_messages
|
|
target.append(result.get_message(width=60))
|
|
|
|
return error_messages, warning_messages
|