xref: /aosp_15_r20/development/scripts/add3prf.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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