Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright (C) 2020 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | """Add files to a Rust package for third party review.""" |
| 17 | |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 18 | import collections |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 19 | import datetime |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 20 | import enum |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 21 | import glob |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 22 | import json |
| 23 | import os |
| 24 | import pathlib |
| 25 | import re |
| 26 | |
| 27 | # patterns to match keys in Cargo.toml |
| 28 | NAME_PATTERN = r"^name *= *\"(.+)\"" |
| 29 | NAME_MATCHER = re.compile(NAME_PATTERN) |
| 30 | VERSION_PATTERN = r"^version *= *\"(.+)\"" |
| 31 | VERSION_MATCHER = re.compile(VERSION_PATTERN) |
| 32 | DESCRIPTION_PATTERN = r"^description *= *(\".+\")" |
| 33 | DESCRIPTION_MATCHER = re.compile(DESCRIPTION_PATTERN) |
| 34 | # NOTE: This description one-liner pattern fails to match |
| 35 | # multi-line descriptions in some Rust crates, e.g. shlex. |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 36 | LICENSE_PATTERN = r"^license *= *\"(.+)\"" |
| 37 | LICENSE_MATCHER = re.compile(LICENSE_PATTERN) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 38 | |
| 39 | # patterns to match year/month/day in METADATA |
| 40 | YMD_PATTERN = r"^ +(year|month|day): (.+)$" |
| 41 | YMD_MATCHER = re.compile(YMD_PATTERN) |
| 42 | YMD_LINE_PATTERN = r"^.* year: *([^ ]+) +month: *([^ ]+) +day: *([^ ]+).*$" |
| 43 | YMD_LINE_MATCHER = re.compile(YMD_LINE_PATTERN) |
| 44 | |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 45 | # patterns to match different licence types in LICENSE* |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 46 | APACHE_PATTERN = r"^.*Apache License.*$" |
| 47 | APACHE_MATCHER = re.compile(APACHE_PATTERN) |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 48 | BOOST_PATTERN = r"^.Boost Software License.*Version 1.0.*$" |
| 49 | BOOST_MATCHER = re.compile(BOOST_PATTERN) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 50 | MIT_PATTERN = r"^.*MIT License.*$" |
| 51 | MIT_MATCHER = re.compile(MIT_PATTERN) |
| 52 | BSD_PATTERN = r"^.*BSD .*License.*$" |
| 53 | BSD_MATCHER = re.compile(BSD_PATTERN) |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 54 | MPL_PATTERN = r"^.Mozilla Public License.*$" |
| 55 | MPL_MATCHER = re.compile(MPL_PATTERN) |
Matt Schulte | b4fa3db | 2023-12-21 08:06:49 -0800 | [diff] [blame] | 56 | UNLICENSE_PATTERN = r"^.*unlicense\.org.*$" |
| 57 | UNLICENSE_MATCHER = re.compile(UNLICENSE_PATTERN) |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 58 | ZERO_BSD_PATTERN = r"^.*Zero-Clause BSD.*$" |
| 59 | ZERO_BSD_MATCHER = re.compile(ZERO_BSD_PATTERN) |
Matt Schulte | 6185205 | 2024-01-18 15:33:30 -0800 | [diff] [blame] | 60 | ZLIB_PATTERN = r"^.*zlib License.$" |
| 61 | ZLIB_MATCHER = re.compile(ZLIB_PATTERN) |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 62 | MULTI_LICENSE_COMMENT = ("# Dual-licensed, using the least restrictive " |
| 63 | "per go/thirdpartylicenses#same.\n ") |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 64 | |
| 65 | # default owners added to OWNERS |
Stephen Hines | ce488a7 | 2023-10-19 00:34:53 -0700 | [diff] [blame] | 66 | DEFAULT_OWNERS = "include platform/prebuilts/rust:main:/OWNERS\n" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 67 | |
| 68 | # See b/159487435 Official policy for rust imports METADATA URLs. |
| 69 | # "license_type: NOTICE" might be optional, |
| 70 | # but it is already used in most rust crate METADATA. |
| 71 | # This line format should match the output of external_updater. |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 72 | METADATA_CONTENT = """name: "{name}" |
| 73 | description: {description} |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 74 | third_party {{ |
Jeongik Cha | 4e8edb4 | 2023-08-29 11:26:17 +0900 | [diff] [blame] | 75 | identifier {{ |
| 76 | type: "crates.io" |
Matt Schulte | a278b15 | 2024-01-23 15:55:53 -0800 | [diff] [blame] | 77 | value: "{name}" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 78 | }} |
Jeongik Cha | 4e8edb4 | 2023-08-29 11:26:17 +0900 | [diff] [blame] | 79 | identifier {{ |
| 80 | type: "Archive" |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 81 | value: "https://static.crates.io/crates/{name}/{name}-{version}.crate" |
Matt Schulte | 46ab4f8 | 2024-01-18 15:17:26 -0800 | [diff] [blame] | 82 | primary_source: true |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 83 | }} |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 84 | version: "{version}" |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 85 | {license_comment}license_type: {license_type} |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 86 | last_upgrade_date {{ |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 87 | year: {year} |
| 88 | month: {month} |
| 89 | day: {day} |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 90 | }} |
| 91 | }} |
| 92 | """ |
| 93 | |
| 94 | |
| 95 | def get_metadata_date(): |
| 96 | """Return last_upgrade_date in METADATA or today.""" |
| 97 | # When applied to existing directories to normalize METADATA, |
| 98 | # we don't want to change the last_upgrade_date. |
| 99 | year, month, day = "", "", "" |
| 100 | if os.path.exists("METADATA"): |
| 101 | with open("METADATA", "r") as inf: |
| 102 | for line in inf: |
| 103 | match = YMD_MATCHER.match(line) |
| 104 | if match: |
| 105 | if match.group(1) == "year": |
| 106 | year = match.group(2) |
| 107 | elif match.group(1) == "month": |
| 108 | month = match.group(2) |
| 109 | elif match.group(1) == "day": |
| 110 | day = match.group(2) |
| 111 | else: |
| 112 | match = YMD_LINE_MATCHER.match(line) |
| 113 | if match: |
| 114 | year, month, day = match.group(1), match.group(2), match.group(3) |
| 115 | if year and month and day: |
| 116 | print("### Reuse date in METADATA:", year, month, day) |
| 117 | return int(year), int(month), int(day) |
| 118 | today = datetime.date.today() |
| 119 | return today.year, today.month, today.day |
| 120 | |
| 121 | |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 122 | def add_metadata(name, version, description, license_group, multi_license): |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 123 | """Update or add METADATA file.""" |
| 124 | if os.path.exists("METADATA"): |
| 125 | print("### Updating METADATA") |
| 126 | else: |
| 127 | print("### Adding METADATA") |
| 128 | year, month, day = get_metadata_date() |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 129 | license_comment = "" |
| 130 | if multi_license: |
| 131 | license_comment = MULTI_LICENSE_COMMENT |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 132 | with open("METADATA", "w") as outf: |
| 133 | outf.write(METADATA_CONTENT.format( |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 134 | name=name, description=description, version=version, |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 135 | license_comment=license_comment, license_type=license_group, year=year, month=month, day=day)) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 136 | |
| 137 | |
| 138 | def grep_license_keyword(license_file): |
| 139 | """Find familiar patterns in a file and return the type.""" |
| 140 | with open(license_file, "r") as input_file: |
| 141 | for line in input_file: |
| 142 | if APACHE_MATCHER.match(line): |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 143 | return License(LicenseType.APACHE2, LicenseGroup.NOTICE, license_file) |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 144 | if BOOST_MATCHER.match(line): |
| 145 | return License(LicenseType.BOOST, LicenseGroup.NOTICE, license_file) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 146 | if MIT_MATCHER.match(line): |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 147 | return License(LicenseType.MIT, LicenseGroup.NOTICE, license_file) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 148 | if BSD_MATCHER.match(line): |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 149 | return License(LicenseType.BSD_LIKE, LicenseGroup.NOTICE, license_file) |
Matt Schulte | 362d7f4 | 2023-12-20 07:54:03 -0800 | [diff] [blame] | 150 | if MPL_MATCHER.match(line): |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 151 | return License(LicenseType.MPL, LicenseGroup.RECIPROCAL, license_file) |
Matt Schulte | b4fa3db | 2023-12-21 08:06:49 -0800 | [diff] [blame] | 152 | if UNLICENSE_MATCHER.match(line): |
| 153 | return License(LicenseType.UNLICENSE, LicenseGroup.PERMISSIVE, license_file) |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 154 | if ZERO_BSD_MATCHER.match(line): |
| 155 | return License(LicenseType.ZERO_BSD, LicenseGroup.PERMISSIVE, license_file) |
Matt Schulte | 6185205 | 2024-01-18 15:33:30 -0800 | [diff] [blame] | 156 | if ZLIB_MATCHER.match(line): |
| 157 | return License(LicenseType.ZLIB, LicenseGroup.NOTICE, license_file) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 158 | print("ERROR: cannot decide license type in", license_file, |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 159 | "assume BSD_LIKE") |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 160 | return License(LicenseType.BSD_LIKE, LicenseGroup.NOTICE, license_file) |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 161 | |
| 162 | |
| 163 | class LicenseType(enum.IntEnum): |
| 164 | """A type of license. |
| 165 | |
| 166 | An IntEnum is used to be able to sort by preference. This is mainly the case |
| 167 | for dual-licensed Apache/MIT code, for which we prefer the Apache license. |
| 168 | The enum name is used to generate the corresponding MODULE_LICENSE_* file. |
| 169 | """ |
| 170 | APACHE2 = 1 |
| 171 | MIT = 2 |
| 172 | BSD_LIKE = 3 |
| 173 | ISC = 4 |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 174 | MPL = 5 |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 175 | ZERO_BSD = 6 |
Matt Schulte | b4fa3db | 2023-12-21 08:06:49 -0800 | [diff] [blame] | 176 | UNLICENSE = 7 |
Matt Schulte | 6185205 | 2024-01-18 15:33:30 -0800 | [diff] [blame] | 177 | ZLIB = 8 |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 178 | BOOST = 9 |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 179 | |
| 180 | class LicenseGroup(enum.Enum): |
| 181 | """A group of license as defined by go/thirdpartylicenses#types |
| 182 | |
| 183 | Note, go/thirdpartylicenses#types calls them "types". But LicenseType was |
| 184 | already taken so this script calls them groups. |
| 185 | """ |
| 186 | RESTRICTED = 1 |
| 187 | RESTRICTED_IF_STATICALLY_LINKED = 2 |
| 188 | RECIPROCAL = 3 |
| 189 | NOTICE = 4 |
| 190 | PERMISSIVE = 5 |
| 191 | BY_EXCEPTION_ONLY = 6 |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 192 | |
| 193 | |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 194 | License = collections.namedtuple('License', ['type', 'group', 'filename']) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 195 | |
| 196 | |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 197 | def decide_license_type(cargo_license): |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 198 | """Check LICENSE* files to determine the license type. |
| 199 | |
| 200 | Returns: A list of Licenses. The first element is the license we prefer. |
| 201 | """ |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 202 | # Most crates.io packages have both APACHE and MIT. |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 203 | # Some crate like time-macros-impl uses lower case names like LICENSE-Apache. |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 204 | licenses = [] |
| 205 | license_file = None |
Matt Schulte | 52e1d5a | 2024-01-18 15:30:30 -0800 | [diff] [blame] | 206 | for license_file in glob.glob("LICENSE*") + glob.glob("COPYING*") + glob.glob("UNLICENSE*"): |
| 207 | lowered_name = os.path.splitext(license_file.lower())[0] |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 208 | if lowered_name == "license-apache": |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 209 | licenses.append(License(LicenseType.APACHE2, LicenseGroup.NOTICE, license_file)) |
Matt Schulte | 1ef53f9 | 2024-01-23 11:16:33 -0800 | [diff] [blame] | 210 | elif lowered_name == "license-boost": |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 211 | licenses.append(License(LicenseType.BOOST, LicenseGroup.NOTICE, license_file)) |
Matt Schulte | 1ef53f9 | 2024-01-23 11:16:33 -0800 | [diff] [blame] | 212 | elif lowered_name == "license-bsd": |
| 213 | licenses.append(License(LicenseType.BSD_LIKE, LicenseGroup.NOTICE, license_file)) |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 214 | elif lowered_name == "license-mit": |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 215 | licenses.append(License(LicenseType.MIT, LicenseGroup.NOTICE, license_file)) |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 216 | elif lowered_name == "license-0bsd": |
| 217 | licenses.append(License(LicenseType.ZERO_BSD, LicenseGroup.PERMISSIVE, license_file)) |
Matt Schulte | 6185205 | 2024-01-18 15:33:30 -0800 | [diff] [blame] | 218 | elif lowered_name == "license-zlib": |
| 219 | licenses.append(License(LicenseType.ZLIB, LicenseGroup.NOTICE, license_file)) |
Matt Schulte | b4fa3db | 2023-12-21 08:06:49 -0800 | [diff] [blame] | 220 | elif lowered_name == "unlicense": |
| 221 | licenses.append(License(LicenseType.UNLICENSE, LicenseGroup.PERMISSIVE, license_file)) |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 222 | if licenses: |
| 223 | licenses.sort(key=lambda l: l.type) |
| 224 | return licenses |
| 225 | if not license_file: |
| 226 | raise FileNotFoundError("No license file has been found.") |
Matthew Maurer | 51ec016 | 2022-08-10 15:29:24 -0700 | [diff] [blame] | 227 | # There is a LICENSE* or COPYING* file, use cargo_license found in |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 228 | # Cargo.toml. |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 229 | if "Apache" in cargo_license: |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 230 | return [License(LicenseType.APACHE2, LicenseGroup.NOTICE, license_file)] |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 231 | if "BSL" in cargo_license: |
| 232 | return [License(LicenseType.BOOST, LicenseGroup.NOTICE, license_file)] |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 233 | if "MIT" in cargo_license: |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 234 | return [License(LicenseType.MIT, LicenseGroup.NOTICE, license_file)] |
Matt Schulte | 38d199e | 2023-12-20 10:05:57 -0800 | [diff] [blame] | 235 | if "0BSD" in cargo_license: |
| 236 | return [License(LicenseType.ZERO_BSD, LicenseGroup.PERMISSIVE, license_file)] |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 237 | if "BSD" in cargo_license: |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 238 | return [License(LicenseType.BSD_LIKE, LicenseGroup.NOTICE, license_file)] |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 239 | if "ISC" in cargo_license: |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 240 | return [License(LicenseType.ISC, LicenseGroup.NOTICE, license_file)] |
| 241 | if "MPL" in cargo_license: |
| 242 | return [License(LicenseType.MPL, LicenseGroup.RECIPROCAL, license_file)] |
Matt Schulte | b4fa3db | 2023-12-21 08:06:49 -0800 | [diff] [blame] | 243 | if "Unlicense" in cargo_license: |
| 244 | return [License(LicenseType.UNLICENSE, LicenseGroup.PERMISSIVE, license_file)] |
Matt Schulte | 6185205 | 2024-01-18 15:33:30 -0800 | [diff] [blame] | 245 | if "Zlib" in cargo_license: |
| 246 | return [License(LicenseType.ZLIB, LicenseGroup.NOTICE, license_file)] |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 247 | return [grep_license_keyword(license_file)] |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 248 | |
| 249 | |
| 250 | def add_notice(): |
| 251 | if not os.path.exists("NOTICE"): |
| 252 | if os.path.exists("LICENSE"): |
| 253 | os.symlink("LICENSE", "NOTICE") |
| 254 | print("Created link from NOTICE to LICENSE") |
| 255 | else: |
| 256 | print("ERROR: missing NOTICE and LICENSE") |
| 257 | |
| 258 | |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 259 | def check_license_link(target): |
| 260 | """Check the LICENSE link, must bet the given target.""" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 261 | if not os.path.islink("LICENSE"): |
| 262 | print("ERROR: LICENSE file is not a link") |
| 263 | return |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 264 | found_target = os.readlink("LICENSE") |
| 265 | if target != found_target and found_target != "LICENSE.txt": |
| 266 | print("ERROR: found LICENSE link to", found_target, |
| 267 | "but expected", target) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 268 | |
| 269 | |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 270 | def add_license(target): |
| 271 | """Add LICENSE link to give target.""" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 272 | if os.path.exists("LICENSE"): |
| 273 | if os.path.islink("LICENSE"): |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 274 | check_license_link(target) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 275 | else: |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 276 | print("NOTE: found LICENSE and it is not a link.") |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 277 | return |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 278 | print("### Creating LICENSE link to", target) |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 279 | os.symlink(target, "LICENSE") |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 280 | |
| 281 | |
| 282 | def add_module_license(license_type): |
| 283 | """Touch MODULE_LICENSE_type file.""" |
| 284 | # Do not change existing MODULE_* files. |
Matt Schulte | 67924d1 | 2024-01-18 15:37:24 -0800 | [diff] [blame] | 285 | for suffix in ["MIT", "APACHE", "APACHE2", "BSD_LIKE", "MPL", "0BSD", "UNLICENSE", "ZLIB", "BOOST"]: |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 286 | module_file = "MODULE_LICENSE_" + suffix |
| 287 | if os.path.exists(module_file): |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 288 | if license_type.name != suffix: |
| 289 | raise Exception("Found unexpected license " + module_file) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 290 | return |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 291 | module_file = "MODULE_LICENSE_" + license_type.name.upper() |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 292 | pathlib.Path(module_file).touch() |
| 293 | print("### Touched", module_file) |
| 294 | |
| 295 | |
| 296 | def found_line(file_name, line): |
| 297 | """Returns true if the given line is found in a file.""" |
| 298 | with open(file_name, "r") as input_file: |
| 299 | return line in input_file |
| 300 | |
| 301 | |
| 302 | def add_owners(): |
| 303 | """Create or append OWNERS with the default owner line.""" |
| 304 | # Existing OWNERS file might contain more than the default owners. |
| 305 | # Only append missing default owners to existing OWNERS. |
| 306 | if os.path.isfile("OWNERS"): |
| 307 | if found_line("OWNERS", DEFAULT_OWNERS): |
| 308 | print("### No change to OWNERS, which has already default owners.") |
| 309 | return |
| 310 | else: |
| 311 | print("### Append default owners to OWNERS") |
| 312 | mode = "a" |
| 313 | else: |
| 314 | print("### Creating OWNERS with default owners") |
| 315 | mode = "w" |
| 316 | with open("OWNERS", mode) as outf: |
| 317 | outf.write(DEFAULT_OWNERS) |
| 318 | |
| 319 | |
| 320 | def toml2json(line): |
| 321 | """Convert a quoted toml string to a json quoted string for METADATA.""" |
| 322 | if line.startswith("\"\"\""): |
| 323 | return "\"()\"" # cannot handle broken multi-line description |
| 324 | # TOML string escapes: \b \t \n \f \r \" \\ (no unicode escape) |
| 325 | line = line[1:-1].replace("\\\\", "\n").replace("\\b", "") |
| 326 | line = line.replace("\\t", " ").replace("\\n", " ").replace("\\f", " ") |
| 327 | line = line.replace("\\r", "").replace("\\\"", "\"").replace("\n", "\\") |
| 328 | # replace a unicode quotation mark, used in the libloading crate |
| 329 | line = line.replace("’", "'") |
| 330 | # strip and escape single quotes |
| 331 | return json.dumps(line.strip()).replace("'", "\\'") |
| 332 | |
| 333 | |
| 334 | def parse_cargo_toml(cargo): |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 335 | """get name, version, description, license string from Cargo.toml.""" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 336 | name = "" |
| 337 | version = "" |
| 338 | description = "" |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 339 | cargo_license = "" |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 340 | with open(cargo, "r") as toml: |
| 341 | for line in toml: |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 342 | if not name and NAME_MATCHER.match(line): |
| 343 | name = NAME_MATCHER.match(line).group(1) |
| 344 | elif not version and VERSION_MATCHER.match(line): |
| 345 | version = VERSION_MATCHER.match(line).group(1) |
| 346 | elif not description and DESCRIPTION_MATCHER.match(line): |
| 347 | description = toml2json(DESCRIPTION_MATCHER.match(line).group(1)) |
| 348 | elif not cargo_license and LICENSE_MATCHER.match(line): |
| 349 | cargo_license = LICENSE_MATCHER.match(line).group(1) |
| 350 | if name and version and description and cargo_license: |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 351 | break |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 352 | return name, version, description, cargo_license |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 353 | |
| 354 | |
| 355 | def main(): |
| 356 | """Add 3rd party review files.""" |
| 357 | cargo = "Cargo.toml" |
| 358 | if not os.path.isfile(cargo): |
| 359 | print("ERROR: ", cargo, "is not found") |
| 360 | return |
| 361 | if not os.access(cargo, os.R_OK): |
| 362 | print("ERROR: ", cargo, "is not readable") |
| 363 | return |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 364 | name, version, description, cargo_license = parse_cargo_toml(cargo) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 365 | if not name or not version or not description: |
| 366 | print("ERROR: Cannot find name, version, or description in", cargo) |
| 367 | return |
Chih-Hung Hsieh | 03f14e4 | 2020-10-19 18:38:30 -0700 | [diff] [blame] | 368 | print("### Cargo.toml license:", cargo_license) |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 369 | licenses = decide_license_type(cargo_license) |
| 370 | preferred_license = licenses[0] |
Matt Schulte | 055ccb3 | 2023-10-30 14:07:27 -0700 | [diff] [blame] | 371 | add_metadata(name, version, description, preferred_license.group.name, len(licenses) > 1) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 372 | add_owners() |
Thiébaud Weksteen | 8da4911 | 2021-02-19 11:59:49 +0100 | [diff] [blame] | 373 | add_license(preferred_license.filename) |
| 374 | add_module_license(preferred_license.type) |
Chih-Hung Hsieh | 3d24aed | 2020-10-05 15:29:11 -0700 | [diff] [blame] | 375 | # It is unclear yet if a NOTICE file is required. |
| 376 | # add_notice() |
| 377 | |
| 378 | |
| 379 | if __name__ == "__main__": |
| 380 | main() |