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