This is an automated email from the ASF dual-hosted git repository.

amaechler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new c0f38c242c2 build: read licenses.yaml versions as strings and fail on 
unknown license names (#20764)
c0f38c242c2 is described below

commit c0f38c242c24abe9b49546e63ed178cb5a14cc4e
Author: Frank Chen <[email protected]>
AuthorDate: Fri Oct 30 05:58:58 2026 +0800

    build: read licenses.yaml versions as strings and fail on unknown license 
names (#20764)
    
    * build: read licenses.yaml versions as strings and fail on unknown license 
names
    
    * build: ignore unknown licenses reported only by excluded modules
    
    * build: fail on any unknown license from released modules and report all 
license errors together
    
    - Look up license names before skipping GPL ones, so mapped names such as 
CDDL + GPLv2 with classpath exception match.
    - Keep the first compatible license, so that later licenses no longer 
override MIT No Attribution or CDDL 1.0.
    - Replace the unsupported-name tracking with find_unknown_licenses(), which 
flags released dependencies whose license is unset.
    - Print unknown licenses with mismatched and missing ones and exit once.
    - Register MIT No Attribution and CDDL 1.0 as names.
    
    * Update distribution/bin/check-licenses.py
    
    Co-authored-by: Andreas Maechler <[email protected]>
    
    * Register org.tukaani:xz 1.12 under Zero-Clause BSD
    
    xz-java switched from public domain to 0BSD in 1.10. The dependency report
    now resolves 'Zero-Clause BSD', so check-licenses flagged the stale
    'Public Domain' registration.
    
    ---------
    
    Co-authored-by: Andreas Maechler <[email protected]>
---
 distribution/bin/check-licenses.py           | 93 +++++++++++++++++++++-------
 distribution/bin/generate-binary-license.py  |  4 +-
 distribution/bin/generate-binary-notice.py   |  4 +-
 distribution/bin/licenses_yaml.py            | 34 ++++++++++
 distribution/bin/refresh-license-versions.py |  9 ++-
 distribution/bin/test_check_licenses.py      | 93 +++++++++++++++++++++++++++-
 licenses.yaml                                | 20 +++---
 licenses/bin/xz.0BSD                         | 13 ++++
 8 files changed, 228 insertions(+), 42 deletions(-)

diff --git a/distribution/bin/check-licenses.py 
b/distribution/bin/check-licenses.py
index dc28ebf77b7..8051266b40c 100755
--- a/distribution/bin/check-licenses.py
+++ b/distribution/bin/check-licenses.py
@@ -15,7 +15,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import yaml
+import licenses_yaml
 import os
 import sys
 from html.parser import HTMLParser
@@ -50,6 +50,7 @@ class DependencyReportParser(HTMLParser):
 
     def parse(self, f):
         self.dep_to_license = {}
+        self.dep_to_license_names = {}
         self.tables_started = 0
         self.tables_finished = 0
         self.feed(f.read())
@@ -162,7 +163,9 @@ class DependencyReportParser(HTMLParser):
                 self.state = "row_end"
                 # print(json.dumps({"groupId": self.group_id, "artifactId": 
self.artifact_id, "version": self.version, "classifier": self.classifier, 
"type": self.dep_type, "license": self.license}))
                 if self.group_id.find("org.apache.druid") < 0:
-                    self.dep_to_license[get_dep_key(self.group_id, 
self.artifact_id, self.version)] = (self.license, self.druid_module_name)
+                    key = get_dep_key(self.group_id, self.artifact_id, 
self.version)
+                    self.dep_to_license[key] = (self.license, 
self.druid_module_name)
+                    self.dep_to_license_names[key] = self.license_names
 
         if self.state == "row_end":
             if tag == "table":
@@ -183,6 +186,7 @@ class DependencyReportParser(HTMLParser):
         self.classifier = None
         self.dep_type = None
         self.license = None
+        self.license_names = []
         self.attr_index = 0
 
     def set_attr(self, data):
@@ -212,20 +216,17 @@ class DependencyReportParser(HTMLParser):
             raise Exception("Unknown attr_index [{}]".format(self.attr_index))
 
     def set_license(self, data):
-        if data.upper().find("GPL") < 0:
-            # Check if the license assosciated with the component is acccepted
-            # set_license() will pick the first acceptable license
-            # this fixes issue where a multi-licensed component
-            # could override accepted license with not accepted one
-            # e.g., EPL / GPL for logback-core
-            if self.license not in self.compatible_license_names:
-                try:
-                    self.license = self.compatible_license_names[data]
-                except KeyError:
-                    print("Unsupported license: " + data)
-                    print("For:" +  self.group_id + " "  + self.artifact_id + 
" in: "+ self.druid_module_name)
-            else:
-                print(self.group_id + " "  + self.artifact_id + " in: " + 
self.druid_module_name + " with: " + self.license + " ignoring " + data)
+        # Skip separator text between license names, in case a report puts ", 
" between the links.
+        if data.strip(", ") == "":
+            return
+        self.license_names.append(data)
+        # Pick the first compatible license, so that a multi-licensed 
component does not override an accepted
+        # license with one that is not, e.g., EPL / LGPL for logback-core. A 
license name missing from
+        # compatible_license_names leaves the license unset, which 
find_unknown_licenses() reports.
+        if self.license is None:
+            self.license = self.compatible_license_names.get(data)
+        else:
+            print(self.group_id + " "  + self.artifact_id + " in: " + 
self.druid_module_name + " with: " + self.license + " ignoring " + data)
 
 
 def print_log_to_stderr(string):
@@ -285,6 +286,7 @@ def build_compatible_license_names():
     compatible_licenses['CDDL + GPLv2 with classpath exception'] = 'CDDL 1.1'
     compatible_licenses['CDDL License'] = 'CDDL 1.1'
     compatible_licenses['COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) 
Version 1.0'] = 'CDDL 1.0'
+    compatible_licenses['CDDL 1.0'] = 'CDDL 1.0'
 
     compatible_licenses['Eclipse Public License 1.0'] = 'Eclipse Public 
License 1.0'
     compatible_licenses['The Eclipse Public License, Version 1.0'] = 'Eclipse 
Public License 1.0'
@@ -330,6 +332,10 @@ def build_compatible_license_names():
     compatible_licenses['MIT license'] = 'MIT License'
 
     compatible_licenses['MIT-0'] = 'MIT No Attribution'
+    compatible_licenses['MIT No Attribution'] = 'MIT No Attribution'
+
+    compatible_licenses['Zero-Clause BSD'] = 'Zero-Clause BSD'
+    compatible_licenses['0BSD'] = 'Zero-Clause BSD'
 
     compatible_licenses['The Go license'] = 'The Go license'
     compatible_licenses['Go License'] = 'The Go license'
@@ -407,10 +413,24 @@ def find_missing_licenses(reported_dep_to_licenses, 
reported_modules, registered
         missing_licenses.append((", ".join(modules),) + key + 
(reported_license_druid_module[0],))
     return missing_licenses
 
+def find_unknown_licenses(reported_dep_to_licenses, reported_modules, 
reported_license_names, skipping_licenses, exclude_module_prefixes):
+    # A dependency that ships in the release must report at least one license 
in compatible_license_names.
+    unknown_licenses = []
+    for key, reported_license_druid_module in reported_dep_to_licenses.items():
+        if reported_license_druid_module[0] is not None or key in 
skipping_licenses:
+            continue
+        shipped_modules = sorted(m for m in reported_modules[key] if not 
is_excluded_module(m, exclude_module_prefixes))
+        if shipped_modules:
+            license_names = ", ".join(reported_license_names.get(key, [])) or 
"<none>"
+            unknown_licenses.append((", ".join(shipped_modules),) + key + 
(license_names,))
+    return unknown_licenses
+
 def read_dependency_reports(dependency_reports_root, compatible_license_names):
-    # Returns (group_id, artifact_id, version) -> (license, druid_module) and 
(group_id, artifact_id, version) -> modules.
+    # Returns (group_id, artifact_id, version) -> (license, druid_module), 
(group_id, artifact_id, version) -> modules,
+    # and (group_id, artifact_id, version) -> license names as reported.
     reported_dep_to_licenses = {}
     reported_modules = {}
+    reported_license_names = {}
     for dirpath, dirnames, filenames in os.walk(dependency_reports_root):
         for filename in filenames:
             if filename == "dependencies.html":
@@ -422,23 +442,26 @@ def read_dependency_reports(dependency_reports_root, 
compatible_license_names):
                     parser = DependencyReportParser(druid_module_name, 
compatible_license_names)
                     dep_to_license = parser.parse(report_file)
                     reported_dep_to_licenses.update(dep_to_license)
+                    reported_license_names.update(parser.dep_to_license_names)
                     for key in dep_to_license:
                         reported_modules.setdefault(key, 
set()).add(druid_module_name)
     if len(reported_dep_to_licenses) == 0:
         raise Exception("No dependency reports are found")
-    return reported_dep_to_licenses, reported_modules
+    return reported_dep_to_licenses, reported_modules, reported_license_names
 
 def check_licenses(license_yaml, dependency_reports_root, 
exclude_module_prefixes):
     # Build a dictionary to facilitate comparing reported licenses and 
registered ones.
     # These dictionaries are the mapping of (group_id, artifact_id, version) 
to license_name.
 
     with open(license_yaml, encoding='utf-8') as registry_file:
-        licenses_list = list(yaml.load_all(registry_file, 
Loader=yaml.FullLoader))
+        licenses_list = licenses_yaml.load_all(registry_file)
     check_license_files(license_yaml, licenses_list)
 
     # Build reported license dictionary.
     compatible_license_names = build_compatible_license_names()
-    reported_dep_to_licenses, reported_modules = 
read_dependency_reports(dependency_reports_root, compatible_license_names)
+    reported_dep_to_licenses, reported_modules, reported_license_names = 
read_dependency_reports(
+        dependency_reports_root, compatible_license_names
+    )
 
     print_log_to_stderr("Found {} reported 
licenses\n".format(len(reported_dep_to_licenses)))
 
@@ -480,6 +503,18 @@ def check_licenses(license_yaml, dependency_reports_root, 
exclude_module_prefixe
         )
         sys.exit(1)
 
+    unknown_license_name_entries = [
+        (license.get('name'), license.get('license_name'))
+        for license in licenses_list
+        if 'libraries' in license and license.get('license_name') not in 
compatible_license_names
+    ]
+    if unknown_license_name_entries:
+        print_log_to_stderr("Error: found {} licenses.yaml entries with 
unknown license names".format(len(unknown_license_name_entries)))
+        for name, license_name in unknown_license_name_entries:
+            print_log_to_stderr("  name: {}, license_name: {}".format(name, 
license_name))
+        print_log_to_stderr("Every license_name must be one of the names in 
build_compatible_license_names() in check-licenses.py.")
+        sys.exit(1)
+
     for license in licenses_list:
         if 'libraries' in license:
             for library in license['libraries']:
@@ -539,6 +574,21 @@ def check_licenses(license_yaml, dependency_reports_root, 
exclude_module_prefixe
             print_log_to_stderr("druid_module: {}, groupId: {}, artifactId: 
{}, version: {}, license: {}".format(missing_license[0], missing_license[1], 
missing_license[2], missing_license[3], missing_license[4]))
         print_log_to_stderr("")
 
+    # Let's find unknown licenses, which are reported by released modules but 
not in compatible_license_names.
+    unknown_licenses = find_unknown_licenses(
+        reported_dep_to_licenses, reported_modules, reported_license_names, 
skipping_licenses, exclude_module_prefixes
+    )
+
+    if len(unknown_licenses) > 0:
+        print_log_to_stderr("Error: found {} dependencies whose reported 
licenses are unknown".format(len(unknown_licenses)))
+        for unknown_license in unknown_licenses:
+            print_log_to_stderr("druid_module: {}, groupId: {}, artifactId: 
{}, version: {}, reported_license: {}".format(*unknown_license))
+        print_log_to_stderr(
+            "If the license is compatible, add its name to 
build_compatible_license_names() in check-licenses.py "
+            "and register the dependency in licenses.yaml under that license."
+        )
+        print_log_to_stderr("")
+
     # Let's find unchecked licenses, which are registered but missing in the 
report.
     # These licenses should be checked manually.
     for key, registered_license in registered_dep_to_licenses.items():
@@ -554,9 +604,10 @@ def check_licenses(license_yaml, dependency_reports_root, 
exclude_module_prefixe
             print_log_to_stderr("groupId: {}, artifactId: {}, version: {}, 
reported_license: {}".format(unchecked_license[0], unchecked_license[1], 
unchecked_license[2], unchecked_license[3]))
     print_log_to_stderr("")
 
-    if len(mismatched_licenses) > 0 or len(missing_licenses) > 0:
+    if len(mismatched_licenses) > 0 or len(missing_licenses) > 0 or 
len(unknown_licenses) > 0:
         print_log_to_stderr("Mismatched licenses: 
{}".format(mismatched_licenses))
         print_log_to_stderr("Missing licenses: {}".format(missing_licenses))
+        print_log_to_stderr("Unknown licenses: {}".format(unknown_licenses))
         sys.exit(1)
 
 
diff --git a/distribution/bin/generate-binary-license.py 
b/distribution/bin/generate-binary-license.py
index ecbeb9cf340..83990962e51 100755
--- a/distribution/bin/generate-binary-license.py
+++ b/distribution/bin/generate-binary-license.py
@@ -15,7 +15,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import yaml
+import licenses_yaml
 import os
 import re
 import sys
@@ -156,7 +156,7 @@ def print_license_name_underbar(license_name):
 
 def load_licenses(license_yaml, exclude_module_prefixes):
     with open(license_yaml, encoding='utf-8') as registry_file:
-        licenses_list = list(yaml.load_all(registry_file, Loader=yaml.Loader))
+        licenses_list = licenses_yaml.load_all(registry_file)
 
     # Filter out entries whose module matches any of the excluded prefixes. 
Used by the release build
     # to skip contrib extension entries (module: extensions-contrib/*) since 
contrib extensions are not
diff --git a/distribution/bin/generate-binary-notice.py 
b/distribution/bin/generate-binary-notice.py
index 464ca28560b..c85561a903e 100755
--- a/distribution/bin/generate-binary-notice.py
+++ b/distribution/bin/generate-binary-notice.py
@@ -17,7 +17,7 @@
 
 import argparse
 import sys
-import yaml
+import licenses_yaml
 
 from collections import defaultdict
 
@@ -68,7 +68,7 @@ def generate_notice(source_notice, dependences_yaml, 
exclude_module_prefixes):
     # Print Apache license first.
     print_outfile(source_notice)
     with open(dependences_yaml, encoding='utf-8') as registry_file:
-        dependencies = list(yaml.load_all(registry_file, Loader=yaml.Loader))
+        dependencies = licenses_yaml.load_all(registry_file)
 
     # Filter out entries whose module matches any of the excluded prefixes. 
Used by the release build
     # to skip contrib extension entries (module: extensions-contrib/*) since 
contrib extensions are not
diff --git a/distribution/bin/licenses_yaml.py 
b/distribution/bin/licenses_yaml.py
new file mode 100644
index 00000000000..c569e3c3f75
--- /dev/null
+++ b/distribution/bin/licenses_yaml.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import yaml
+
+
+class LicensesYamlLoader(yaml.SafeLoader):
+    # Loads plain scalars that look like numbers as strings, so that versions 
such as 1.10 do not need to be
+    # quoted in licenses.yaml. With the default resolvers, 1.10 would be 
loaded as the float 1.1.
+    pass
+
+
+LicensesYamlLoader.yaml_implicit_resolvers = {
+    first_char: [(tag, regexp) for tag, regexp in resolvers if tag not in 
('tag:yaml.org,2002:int', 'tag:yaml.org,2002:float')]
+    for first_char, resolvers in 
yaml.SafeLoader.yaml_implicit_resolvers.items()
+}
+
+
+def load_all(stream):
+    return list(yaml.load_all(stream, Loader=LicensesYamlLoader))
diff --git a/distribution/bin/refresh-license-versions.py 
b/distribution/bin/refresh-license-versions.py
index 0988adbb4ac..46e450e16b0 100755
--- a/distribution/bin/refresh-license-versions.py
+++ b/distribution/bin/refresh-license-versions.py
@@ -23,6 +23,7 @@ import re
 import sys
 import types
 
+import licenses_yaml
 import yaml
 
 spec = importlib.util.spec_from_file_location(
@@ -59,9 +60,7 @@ def unquote(raw):
 
 
 def format_version(version, quote):
-    # Quote when the old value was quoted, or when YAML would not read the new 
value back as a string (2.22 is a float).
-    if quote is None and not isinstance(yaml.safe_load(version), str):
-        quote = '"'
+    # Keep the old quotes. Unquoted versions need none: licenses_yaml reads 
2.10 as the string "2.10".
     return version if quote is None else quote + version + quote
 
 
@@ -86,7 +85,7 @@ def refresh_versions(text, reported_modules):
     # A reported version that another entry already registers belongs to that 
entry, not to a stale one.
     registered = {}
     for lines in documents:
-        parsed = yaml.safe_load("".join(lines))
+        parsed = yaml.load("".join(lines), 
Loader=licenses_yaml.LicensesYamlLoader)
         line_index, match = find_version_line(lines)
         if not isinstance(parsed, dict) or "libraries" not in parsed or match 
is None or not match.group(1):
             continue
@@ -178,7 +177,7 @@ def main():
 
     # The report parser prints notes about multi-licensed libraries; keep 
stdout for the changes below.
     with contextlib.redirect_stdout(sys.stderr):
-        _, reported_modules = check_licenses.read_dependency_reports(
+        _, reported_modules, _ = check_licenses.read_dependency_reports(
             args.dependency_reports_root, 
check_licenses.build_compatible_license_names()
         )
 
diff --git a/distribution/bin/test_check_licenses.py 
b/distribution/bin/test_check_licenses.py
index 3a6b9c16424..4eee01a8bee 100755
--- a/distribution/bin/test_check_licenses.py
+++ b/distribution/bin/test_check_licenses.py
@@ -16,6 +16,7 @@
 # limitations under the License.
 
 import importlib.util
+import io
 import os
 import unittest
 
@@ -57,6 +58,30 @@ class FindMissingLicensesTest(unittest.TestCase):
         self.assertEqual([], find_missing([AZURE], [], skipping={GAV: {}}))
 
 
+def find_unknown(license, reporting_modules, skipping=None):
+    return check_licenses.find_unknown_licenses(
+        {GAV: (license, reporting_modules[0])}, {GAV: set(reporting_modules)}, 
{GAV: ["Some New License"]}, skipping or {}, EXCLUDED
+    )
+
+
+class FindUnknownLicensesTest(unittest.TestCase):
+    def test_reported_by_released_module(self):
+        self.assertEqual([(AZURE,) + GAV + ("Some New License",)], 
find_unknown(None, [AZURE]))
+
+    def test_reported_only_by_excluded_modules_is_ignored(self):
+        self.assertEqual([], find_unknown(None, [TESTCONTAINERS, RANGER]))
+
+    def test_reported_by_released_and_excluded_modules(self):
+        self.assertEqual([(AZURE,) + GAV + ("Some New License",)], 
find_unknown(None, [TESTCONTAINERS, AZURE]))
+
+    def test_known_and_unspecified_licenses_are_ignored(self):
+        self.assertEqual([], find_unknown(APACHE, [AZURE]))
+        self.assertEqual([], find_unknown("-", [AZURE]))
+
+    def test_skipped_dependency_is_ignored(self):
+        self.assertEqual([], find_unknown(None, [AZURE], skipping={GAV: {}}))
+
+
 class FindDruidModuleNameTest(unittest.TestCase):
     def test_maps_report_dirs_to_module_tags(self):
         self.assertEqual("java-core", 
check_licenses.find_druid_module_name("target/license-reports/core/reports"))
@@ -102,14 +127,14 @@ class RefreshVersionsTest(unittest.TestCase):
         _, updated, _ = self.refresh(text, reports(("org.foo:a:1.0.1", 
"java-core")))
         self.assertEqual([("Foo", "java-core", "1.0.0", "1.0.1")], updated)
 
-    def test_keeps_quotes_and_quotes_versions_yaml_would_read_as_numbers(self):
+    def 
test_keeps_quotes_and_does_not_quote_versions_that_look_like_numbers(self):
         text = licenses_yaml(
             entry("Quoted", "java-core", "'2.21'", ["org.q: a"]),
             entry("Plain", "java-core", "1.0.0", ["org.p: a"]),
         )
-        new_text, _, _ = self.refresh(text, reports(("org.q:a:2.22", 
"java-core"), ("org.p:a:2", "java-core")))
+        new_text, _, _ = self.refresh(text, reports(("org.q:a:2.22", 
"java-core"), ("org.p:a:2.10", "java-core")))
         self.assertIn("version: '2.22'\n", new_text)
-        self.assertIn('version: "2"\n', new_text)
+        self.assertIn("version: 2.10\n", new_text)
 
     def test_leaves_current_entries_alone(self):
         text = licenses_yaml(entry("Foo", "java-core", "1.0", ["org.foo: a"]))
@@ -213,5 +238,67 @@ class RefreshVersionsTest(unittest.TestCase):
         self.assertEqual((text, [], []), self.refresh(text, 
reports(("org.s:a:1.1", "java-core"), ("org.c:a:1.1", "java-core"))))
 
 
+def parse_report(*licenses):
+    row = ", ".join("<a>{}</a>".format(license) for license in licenses)
+    report = (
+        '<a id="Project_Dependencies_compile"></a><table>'
+        
"<tr><th>GroupId</th><th>ArtifactId</th><th>Version</th><th>Type</th><th>Licenses</th></tr>"
+        
"<tr><td>org.tukaani</td><td>xz</td><td>1.10</td><td>jar</td><td>{}</td></tr>"
+        "</table>".format(row)
+    )
+    parser = check_licenses.DependencyReportParser("java-core", 
check_licenses.build_compatible_license_names())
+    dep_to_license = parser.parse(io.StringIO(report))
+    return dep_to_license, parser.dep_to_license_names
+
+
+class DependencyReportParserTest(unittest.TestCase):
+    XZ = check_licenses.get_dep_key("org.tukaani", "xz", "1.10")
+
+    def license_of(self, *licenses):
+        return parse_report(*licenses)[0][self.XZ][0]
+
+    def test_known_license_is_normalized(self):
+        self.assertEqual(({self.XZ: ("Zero-Clause BSD", "java-core")}, 
{self.XZ: ["0BSD"]}), parse_report("0BSD"))
+
+    def test_unknown_license_is_unset_and_its_name_is_kept(self):
+        self.assertEqual(({self.XZ: (None, "java-core")}, {self.XZ: ["Some New 
License"]}), parse_report("Some New License"))
+
+    def test_unknown_license_is_fine_when_another_license_is_known(self):
+        self.assertEqual(APACHE, self.license_of("Some New License", 
"Apache-2.0"))
+
+    def test_gpl_only_license_is_unset(self):
+        self.assertIsNone(self.license_of("GNU Lesser General Public License, 
Version 2.1"))
+
+    def test_gpl_license_does_not_override_a_compatible_one(self):
+        self.assertEqual("Eclipse Public License 1.0", 
self.license_of("LGPL-2.1", "Eclipse Public License - v 1.0", "LGPL-2.1"))
+
+    def test_known_license_mentioning_gpl_is_normalized(self):
+        self.assertEqual("CDDL 1.1", self.license_of("CDDL + GPLv2 with 
classpath exception"))
+
+    def test_separators_are_not_license_names(self):
+        self.assertEqual({self.XZ: ["Some New License", "LGPL-2.1"]}, 
parse_report("Some New License", "LGPL-2.1")[1])
+
+    def test_unspecified_license_is_kept(self):
+        self.assertEqual("-", self.license_of("-", "Apache-2.0"))
+
+    def test_first_compatible_license_is_kept(self):
+        self.assertEqual("MIT No Attribution", self.license_of("MIT-0", 
"Apache-2.0"))
+
+
+class CompatibleLicenseNamesTest(unittest.TestCase):
+    def test_every_normalized_name_is_a_name(self):
+        compatible_license_names = 
check_licenses.build_compatible_license_names()
+        self.assertEqual([], sorted(set(compatible_license_names.values()) - 
set(compatible_license_names)))
+
+
+class LoadLicensesYamlTest(unittest.TestCase):
+    def test_versions_that_look_like_numbers_are_loaded_as_strings(self):
+        docs = check_licenses.licenses_yaml.load_all("version: 
1.10\n---\nversion: 2\n---\nversion: 1.5.7-11\n")
+        self.assertEqual(["1.10", "2", "1.5.7-11"], [doc["version"] for doc in 
docs])
+
+    def test_booleans_are_still_loaded_as_booleans(self):
+        self.assertEqual([{"skip_dependency_report_check": True}], 
check_licenses.licenses_yaml.load_all("skip_dependency_report_check: true\n"))
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/licenses.yaml b/licenses.yaml
index ca076a1bbc9..2119cd8ffa0 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -398,7 +398,7 @@ name: Jackson
 license_category: binary
 module: java-core
 license_name: Apache License version 2.0
-version: "2.22"
+version: 2.22
 libraries:
   - com.fasterxml.jackson.core: jackson-annotations
 
@@ -1011,7 +1011,7 @@ name: com.nimbusds nimbus-jose-jwt
 license_category: binary
 module: extensions/druid-pac4j
 license_name: Apache License version 2.0
-version: "10.10"
+version: 10.10
 libraries:
   - com.nimbusds: nimbus-jose-jwt
 
@@ -1426,7 +1426,7 @@ name: org.bouncycastle bcprov-jdk18on
 license_category: binary
 module: extensions/druid-kubernetes-extensions
 license_name: MIT License
-version: "1.86"
+version: 1.86
 libraries:
   - org.bouncycastle: bcprov-jdk18on
   - org.bouncycastle: bcprov-ext-jdk18on
@@ -1437,7 +1437,7 @@ name: org.bouncycastle bcpkix-jdk18on and bcutil-jdk18on
 license_category: binary
 module: extensions/druid-kubernetes-extensions
 license_name: MIT License
-version: "1.86"
+version: 1.86
 libraries:
   - org.bouncycastle: bcpkix-jdk18on
   - org.bouncycastle: bcutil-jdk18on
@@ -2990,7 +2990,7 @@ libraries:
 ---
 
 name: jersey-common
-version: '2.30'
+version: 2.30
 license_category: binary
 module: extensions/druid-avro-extensions
 license_name: Apache License version 2.0
@@ -3557,7 +3557,7 @@ name: aircompressor
 license_category: binary
 module: extensions/druid-parquet-extensions
 license_name: Apache License version 2.0
-version: "2.0.3"
+version: 2.0.3
 libraries:
   - io.airlift: aircompressor
 
@@ -4012,8 +4012,10 @@ libraries:
 name: XZ
 license_category: binary
 module: java-core
-license_name: Public Domain
+license_name: Zero-Clause BSD
+copyright: The XZ for Java authors and contributors
 version: 1.12
+license_file_path: licenses/bin/xz.0BSD
 libraries:
   - org.tukaani: xz
 
@@ -4125,7 +4127,7 @@ name: aircompressor
 license_category: binary
 module: extensions/druid-orc-extensions
 license_name: Apache License version 2.0
-version: "2.0.3"
+version: 2.0.3
 libraries:
   - io.airlift: aircompressor
 
@@ -7546,7 +7548,7 @@ license_category: binary
 module: extensions/druid-kerberos
 license_name: Eclipse Public License 2.0
 homepage: https://projects.eclipse.org/projects/ee4j.jersey
-version: '2.46'
+version: 2.46
 libraries:
   - org.glassfish.jersey.containers: jersey-container-servlet
   - org.glassfish.jersey.containers: jersey-container-servlet-core
diff --git a/licenses/bin/xz.0BSD b/licenses/bin/xz.0BSD
new file mode 100644
index 00000000000..16270b71eee
--- /dev/null
+++ b/licenses/bin/xz.0BSD
@@ -0,0 +1,13 @@
+Copyright (C) The XZ for Java authors and contributors
+
+Permission to use, copy, modify, and/or distribute this
+software for any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to