[ssci] Defined License metadata field
Bug: b:277147404 Change-Id: I498537328c0908417955eda34ad48ca46f4275aa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4776734 Reviewed-by: Rachael Newitt <renewitt@google.com> Auto-Submit: Anne Redulla <aredulla@google.com> Commit-Queue: Rachael Newitt <renewitt@google.com>changes/34/4776734/10
parent
33159ff3e8
commit
c7350c496a
@ -0,0 +1,131 @@
|
||||
#!/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 re
|
||||
import sys
|
||||
from typing import List, Union, Tuple
|
||||
|
||||
_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 metadata.fields.types as field_types
|
||||
import metadata.fields.util as util
|
||||
import metadata.validation_result as vr
|
||||
|
||||
# Copied from ANDROID_ALLOWED_LICENSES in
|
||||
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/PRESUBMIT.py
|
||||
_ANDROID_ALLOWED_LICENSES = [
|
||||
"A(pple )?PSL 2(\.0)?",
|
||||
"Android Software Development Kit License",
|
||||
"Apache( License)?,?( Version)? 2(\.0)?",
|
||||
"(New )?([23]-Clause )?BSD( [23]-Clause)?( with advertising clause)?",
|
||||
"GNU Lesser Public License",
|
||||
"L?GPL ?v?2(\.[01])?( or later)?( with the classpath exception)?",
|
||||
"(The )?MIT(/X11)?(-like)?( License)?",
|
||||
"MPL 1\.1 ?/ ?GPL 2(\.0)? ?/ ?LGPL 2\.1",
|
||||
"MPL 2(\.0)?",
|
||||
"Microsoft Limited Public License",
|
||||
"Microsoft Permissive License",
|
||||
"Public Domain",
|
||||
"Python",
|
||||
"SIL Open Font License, Version 1.1",
|
||||
"SGI Free Software License B",
|
||||
"Unicode, Inc. License",
|
||||
"University of Illinois\/NCSA Open Source",
|
||||
"X11",
|
||||
"Zlib",
|
||||
]
|
||||
_PATTERN_LICENSE_ALLOWED = re.compile(
|
||||
"^({})$".format("|".join(_ANDROID_ALLOWED_LICENSES)),
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_PATTERN_VERBOSE_DELIMITER = re.compile(r" and | or | / ")
|
||||
|
||||
|
||||
def process_license_value(value: str,
|
||||
atomic_delimiter: str) -> List[Tuple[str, bool]]:
|
||||
"""Process a license field value, which may list multiple licenses.
|
||||
|
||||
Args:
|
||||
value: the value to process, which may include both verbose and atomic
|
||||
delimiters, e.g. "Apache, 2.0 and MIT and custom"
|
||||
atomic_delimiter: the delimiter to use as a final step; values will not be
|
||||
further split after using this delimiter.
|
||||
|
||||
Returns: a list of the constituent licenses within the given value,
|
||||
and whether the constituent license is on the allowlist.
|
||||
e.g. [("Apache, 2.0", True), ("MIT", True), ("custom", False)]
|
||||
"""
|
||||
# Check if the value is on the allowlist as-is, and thus does not require
|
||||
# further processing.
|
||||
if is_license_allowlisted(value):
|
||||
return [(value, True)]
|
||||
|
||||
breakdown = []
|
||||
if re.search(_PATTERN_VERBOSE_DELIMITER, value):
|
||||
# Split using the verbose delimiters.
|
||||
for component in re.split(_PATTERN_VERBOSE_DELIMITER, value):
|
||||
breakdown.extend(
|
||||
process_license_value(component.strip(), atomic_delimiter))
|
||||
else:
|
||||
# Split using the standard value delimiter. This results in atomic values;
|
||||
# there is no further splitting possible.
|
||||
for atomic_value in value.split(atomic_delimiter):
|
||||
atomic_value = atomic_value.strip()
|
||||
breakdown.append((atomic_value, is_license_allowlisted(atomic_value)))
|
||||
|
||||
return breakdown
|
||||
|
||||
|
||||
def is_license_allowlisted(value: str) -> bool:
|
||||
"""Returns whether the value is in the allowlist for license types."""
|
||||
return util.matches(_PATTERN_LICENSE_ALLOWED, value)
|
||||
|
||||
|
||||
class LicenseField(field_types.MetadataField):
|
||||
"""Custom field for the package's license type(s).
|
||||
|
||||
e.g. Apache 2.0, MIT, BSD, Public Domain.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(name="License", one_liner=False)
|
||||
|
||||
def validate(self, value: str) -> Union[vr.ValidationResult, None]:
|
||||
"""Checks the given value consists of recognized license types.
|
||||
|
||||
Note: this field supports multiple values.
|
||||
"""
|
||||
not_allowlisted = []
|
||||
licenses = process_license_value(value,
|
||||
atomic_delimiter=self.VALUE_DELIMITER)
|
||||
for license, allowed in licenses:
|
||||
if util.is_empty(license):
|
||||
return vr.ValidationError(f"{self._name} has an empty value.")
|
||||
if not allowed:
|
||||
not_allowlisted.append(license)
|
||||
|
||||
if not_allowlisted:
|
||||
template = ("{field_name} has licenses not in the allowlist. If "
|
||||
"there are multiple license types, separate them with a "
|
||||
"'{delim}'. Invalid values: {values}.")
|
||||
message = template.format(field_name=self._name,
|
||||
delim=self.VALUE_DELIMITER,
|
||||
values=util.quoted(not_allowlisted))
|
||||
return vr.ValidationWarning(message)
|
||||
|
||||
# Suggest using the standard value delimiter when possible.
|
||||
if (re.search(_PATTERN_VERBOSE_DELIMITER, value)
|
||||
and self.VALUE_DELIMITER not in value):
|
||||
return vr.ValidationWarning(
|
||||
f"{self._name} should use '{self.VALUE_DELIMITER}' to delimit "
|
||||
"values.")
|
||||
|
||||
return None
|
Loading…
Reference in New Issue