blob: 442bd1ea993ebcc37fb3bce98d8a25bbfb22b08e [file] [log] [blame]
Anne Redullac7aca342023-08-25 01:09:08 +00001#!/usr/bin/env python3
2# Copyright 2023 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import os
7import sys
Anne Redullab9d7c852023-08-29 23:54:27 +00008from typing import Callable, List, Tuple, Union
Anne Redullac7aca342023-08-25 01:09:08 +00009
10_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
11# The repo's root directory.
12_ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, ".."))
13
14# Add the repo's root directory for clearer imports.
15sys.path.insert(0, _ROOT_DIR)
16
Anne Redullab9d7c852023-08-29 23:54:27 +000017import gclient_utils
Anne Redullac7aca342023-08-25 01:09:08 +000018import metadata.parse
19import metadata.validation_result as vr
20
21
Anne Redullaed9a0812023-09-20 03:08:25 +000022_TRANSITION_PRESCRIPT = (
23 "The following issue should be addressed now, as it will become a "
24 "presubmit error (instead of warning) once third party metadata "
25 "validation is enforced.\nThird party metadata issue:")
26
27
Anne Redullab9d7c852023-08-29 23:54:27 +000028def validate_content(content: str, source_file_dir: str,
29 repo_root_dir: str) -> List[vr.ValidationResult]:
Anne Redulla67157582023-09-04 22:02:36 +000030 """Validate the content as a metadata file.
Anne Redullab9d7c852023-08-29 23:54:27 +000031
Anne Redulla67157582023-09-04 22:02:36 +000032 Args:
33 content: the entire content of a file to be validated as a
34 metadata file.
35 source_file_dir: the directory of the metadata file that the
36 license file value is from; this is needed to
37 construct file paths to license files.
38 repo_root_dir: the repository's root directory; this is needed
39 to construct file paths to license files.
Anne Redullab9d7c852023-08-29 23:54:27 +000040
Anne Redulla67157582023-09-04 22:02:36 +000041 Returns: the validation results for the given content.
42 """
43 results = []
44 dependencies = metadata.parse.parse_content(content)
45 if not dependencies:
46 result = vr.ValidationError(reason="No dependency metadata found.")
47 return [result]
Anne Redullac7aca342023-08-25 01:09:08 +000048
Anne Redulla67157582023-09-04 22:02:36 +000049 for dependency in dependencies:
50 dependency_results = dependency.validate(
51 source_file_dir=source_file_dir, repo_root_dir=repo_root_dir)
52 results.extend(dependency_results)
53 return results
Anne Redullac7aca342023-08-25 01:09:08 +000054
55
Mike Frysinger124bb8e2023-09-06 05:48:55 +000056def _construct_file_read_error(filepath: str, cause: str) -> vr.ValidationError:
Anne Redulla67157582023-09-04 22:02:36 +000057 """Helper function to create a validation error for a
58 file reading issue.
59 """
60 result = vr.ValidationError(
61 reason="Cannot read metadata file.",
62 additional=[f"Attempted to read '{filepath}' but {cause}."])
63 return result
Anne Redullab9d7c852023-08-29 23:54:27 +000064
65
66def validate_file(
67 filepath: str,
68 repo_root_dir: str,
69 reader: Callable[[str], Union[str, bytes]] = None,
70) -> List[vr.ValidationResult]:
Anne Redulla67157582023-09-04 22:02:36 +000071 """Validate the item located at the given filepath is a valid
72 dependency metadata file.
Anne Redullab9d7c852023-08-29 23:54:27 +000073
Anne Redulla67157582023-09-04 22:02:36 +000074 Args:
75 filepath: the path to validate, e.g.
76 "/chromium/src/third_party/libname/README.chromium"
77 repo_root_dir: the repository's root directory; this is needed
78 to construct file paths to license files.
79 reader (optional): callable function/method to read the content
80 of the file.
Anne Redullab9d7c852023-08-29 23:54:27 +000081
Anne Redulla67157582023-09-04 22:02:36 +000082 Returns: the validation results for the given filepath and its
83 contents, if it exists.
84 """
85 if reader is None:
86 reader = gclient_utils.FileRead
Anne Redullab9d7c852023-08-29 23:54:27 +000087
Anne Redulla67157582023-09-04 22:02:36 +000088 try:
89 content = reader(filepath)
90 except FileNotFoundError:
91 return [_construct_file_read_error(filepath, "file not found")]
92 except PermissionError:
93 return [_construct_file_read_error(filepath, "access denied")]
94 except Exception as e:
95 return [
96 _construct_file_read_error(filepath, f"unexpected error: '{e}'")
97 ]
98 else:
99 if not isinstance(content, str):
100 return [_construct_file_read_error(filepath, "decoding failed")]
Anne Redullab9d7c852023-08-29 23:54:27 +0000101
Anne Redulla67157582023-09-04 22:02:36 +0000102 # Get the directory the metadata file is in.
103 source_file_dir = os.path.dirname(filepath)
104 return validate_content(content=content,
105 source_file_dir=source_file_dir,
106 repo_root_dir=repo_root_dir)
Anne Redullab9d7c852023-08-29 23:54:27 +0000107
108
109def check_file(
110 filepath: str,
111 repo_root_dir: str,
112 reader: Callable[[str], Union[str, bytes]] = None,
113) -> Tuple[List[str], List[str]]:
Anne Redulla67157582023-09-04 22:02:36 +0000114 """Run metadata validation on the given filepath, and return all
115 validation errors and validation warnings.
Anne Redullac7aca342023-08-25 01:09:08 +0000116
Anne Redulla67157582023-09-04 22:02:36 +0000117 Args:
118 filepath: the path to a metadata file, e.g.
119 "/chromium/src/third_party/libname/README.chromium"
120 repo_root_dir: the repository's root directory; this is needed
121 to construct file paths to license files.
122 reader (optional): callable function/method to read the content
123 of the file.
Anne Redullac7aca342023-08-25 01:09:08 +0000124
Anne Redulla67157582023-09-04 22:02:36 +0000125 Returns:
126 error_messages: the fatal validation issues present in the file;
127 i.e. presubmit should fail.
128 warning_messages: the non-fatal validation issues present in the
129 file; i.e. presubmit should still pass.
130 """
131 results = validate_file(filepath=filepath,
132 repo_root_dir=repo_root_dir,
133 reader=reader)
Anne Redullab9d7c852023-08-29 23:54:27 +0000134
Anne Redulla67157582023-09-04 22:02:36 +0000135 error_messages = []
136 warning_messages = []
137 for result in results:
Anne Redulla67157582023-09-04 22:02:36 +0000138 # TODO(aredulla): Actually distinguish between validation errors
139 # and warnings. The quality of metadata is currently being
140 # uplifted, but is not yet guaranteed to pass validation. So for
141 # now, all validation results will be returned as warnings so
142 # CLs are not blocked by invalid metadata in presubmits yet.
143 # Bug: b/285453019.
Anne Redullaed9a0812023-09-20 03:08:25 +0000144 if result.is_fatal():
145 message = result.get_message(prescript=_TRANSITION_PRESCRIPT,
146 width=60)
147 else:
148 message = result.get_message(width=60)
Anne Redulla67157582023-09-04 22:02:36 +0000149 warning_messages.append(message)
Anne Redullac7aca342023-08-25 01:09:08 +0000150
Anne Redulla67157582023-09-04 22:02:36 +0000151 return error_messages, warning_messages