|  | #!/usr/bin/env python | 
|  |  | 
|  | # reads bazel query XML files, to join target names with their licenses. | 
|  |  | 
|  | from __future__ import print_function | 
|  | from collections import namedtuple | 
|  |  | 
|  | import argparse | 
|  | import json | 
|  | from collections import defaultdict | 
|  | from sys import stdout, stderr | 
|  | import xml.etree.ElementTree as ET | 
|  |  | 
|  | DO_NOT_DISTRIBUTE = "//lib:LICENSE-DO_NOT_DISTRIBUTE" | 
|  |  | 
|  | LICENSE_PREFIX = "//lib:LICENSE-" | 
|  |  | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument("--asciidoctor", action="store_true") | 
|  | parser.add_argument("--json-map", action="append", dest="json_maps") | 
|  | parser.add_argument("xmls", nargs="+") | 
|  | args = parser.parse_args() | 
|  |  | 
|  |  | 
|  | def read_file(filename): | 
|  | "Reads file and returns its content" | 
|  | with open(filename) as fd: | 
|  | return fd.read() | 
|  |  | 
|  | # List of files in package to which license is applied. | 
|  | # kind - enum, one of | 
|  | #   AllFiles - license applies to all files in package | 
|  | #   OnlySpecificFiles - license applies to all files from "files" list | 
|  | #   AllFilesExceptSpecific - license applies to all files in package | 
|  | #      except several files. "files" contains list of exceptions | 
|  | # files - defines list of files for the following kinds: | 
|  | #   OnlySpecificFiles, AllFilesExceptSpecific. | 
|  | #   Each item is a string, but not necessary a real name of a file. | 
|  | #   It can be any string, understandable by human (like directory name) | 
|  | LicensedFiles = namedtuple("LicensedFiles", ["kind", "files"]) | 
|  |  | 
|  | # PackageInfo - contains information about pacakge/files in packages to | 
|  | #   which license is applied. | 
|  | # name - name of the package, as specified in package.json file | 
|  | # version - optional package version. Exists only if different versions | 
|  | #   of the same package have different licenses | 
|  | # licensed_files - instance of LicensedFiles | 
|  | PackageInfo = namedtuple("PackageInfo", ["name", "version", "licensed_files"]) | 
|  |  | 
|  | # LicenseMapItem - describe one type of license and a list of packages | 
|  | #   under this license | 
|  | # name - name of the license | 
|  | # safename - name which is safe to use as an asciidoc bookmark name | 
|  | # packages - list of PackageInfo | 
|  | # license_text - license text as string | 
|  | LicenseMapItem = namedtuple("LicenseMapItem", | 
|  | ["name", "safename", "packages", "license_text"]) | 
|  |  | 
|  |  | 
|  | def load_xmls(xml_filenames): | 
|  | """Load xml files produced by bazel query | 
|  | and converts them to a list of LicenseMapItem | 
|  |  | 
|  | Args: | 
|  | xml_filenames: list of string; each string is a filename | 
|  | Returns: | 
|  | list of LicenseMapItem | 
|  | """ | 
|  | entries = defaultdict(list) | 
|  | graph = defaultdict(list) | 
|  | handled_rules = set() | 
|  | for xml in xml_filenames: | 
|  | tree = ET.parse(xml) | 
|  | root = tree.getroot() | 
|  |  | 
|  | for child in root: | 
|  | rule_name = child.attrib["name"] | 
|  | if rule_name in handled_rules: | 
|  | # already handled in other xml files | 
|  | continue | 
|  |  | 
|  | handled_rules.add(rule_name) | 
|  | for c in list(child): | 
|  | if c.tag != "rule-input": | 
|  | continue | 
|  |  | 
|  | license_name = c.attrib["name"] | 
|  | if LICENSE_PREFIX in license_name: | 
|  | entries[rule_name].append(license_name) | 
|  | graph[license_name].append(rule_name) | 
|  |  | 
|  | if len(graph[DO_NOT_DISTRIBUTE]): | 
|  | print("DO_NOT_DISTRIBUTE license found in:", file=stderr) | 
|  | for target in graph[DO_NOT_DISTRIBUTE]: | 
|  | print(target, file=stderr) | 
|  | exit(1) | 
|  |  | 
|  | result = [] | 
|  | for n in sorted(graph.keys()): | 
|  | if len(graph[n]) == 0: | 
|  | continue | 
|  |  | 
|  | name = n[len(LICENSE_PREFIX):] | 
|  | safename = name.replace(".", "_") | 
|  | packages_names = [] | 
|  | for d in sorted(graph[n]): | 
|  | if d.startswith("//lib:") or d.startswith("//lib/"): | 
|  | p = d[len("//lib:"):] | 
|  | else: | 
|  | p = d[d.index(":") + 1:].lower() | 
|  | if "__" in p: | 
|  | p = p[:p.index("__")] | 
|  | packages_names.append(p) | 
|  |  | 
|  | filename = n[2:].replace(":", "/") | 
|  | content = read_file(filename) | 
|  | result.append(LicenseMapItem( | 
|  | name=name, | 
|  | safename=safename, | 
|  | license_text=content, | 
|  | packages=[PackageInfo(name=name, version=None, | 
|  | licensed_files=LicensedFiles(kind="All", | 
|  | files=[])) for | 
|  | name | 
|  | in packages_names] | 
|  | ) | 
|  | ) | 
|  |  | 
|  | return result | 
|  |  | 
|  | def main(): | 
|  | xml_data = load_xmls(args.xmls) | 
|  | json_map_data = load_jsons(args.json_maps) | 
|  |  | 
|  | if args.asciidoctor: | 
|  | # We don't want any blank line before "= Gerrit Code Review - Licenses" | 
|  | print("""= Gerrit Code Review - Licenses | 
|  |  | 
|  | // DO NOT EDIT - GENERATED AUTOMATICALLY. | 
|  |  | 
|  | Gerrit open source software is licensed under the <<Apache2_0,Apache | 
|  | License 2.0>>.  Executable distributions also include other software | 
|  | components that are provided under additional licenses. | 
|  |  | 
|  | [[cryptography]] | 
|  | == Cryptography Notice | 
|  |  | 
|  | This distribution includes cryptographic software.  The country | 
|  | in which you currently reside may have restrictions on the import, | 
|  | possession, use, and/or re-export to another country, of encryption | 
|  | software.  BEFORE using any encryption software, please check | 
|  | your country's laws, regulations and policies concerning the | 
|  | import, possession, or use, and re-export of encryption software, | 
|  | to see if this is permitted.  See the | 
|  | link:http://www.wassenaar.org/[Wassenaar Arrangement] | 
|  | for more information. | 
|  |  | 
|  | The U.S. Government Department of Commerce, Bureau of Industry | 
|  | and Security (BIS), has classified this software as Export | 
|  | Commodity Control Number (ECCN) 5D002.C.1, which includes | 
|  | information security software using or performing cryptographic | 
|  | functions with asymmetric algorithms.  The form and manner of | 
|  | this distribution makes it eligible for export under the License | 
|  | Exception ENC Technology Software Unrestricted (TSU) exception | 
|  | (see the BIS Export Administration Regulations, Section 740.13) | 
|  | for both object code and source code. | 
|  |  | 
|  | Gerrit includes an SSH daemon (Apache SSHD), to support authenticated | 
|  | uploads of changes directly from `git push` command line clients. | 
|  |  | 
|  | Gerrit includes an SSH client (JSch), to support authenticated | 
|  | replication of changes to remote systems, such as for automatic | 
|  | updates of mirror servers, or realtime backups. | 
|  |  | 
|  | == Licenses | 
|  | """) | 
|  |  | 
|  | for data in xml_data + json_map_data: | 
|  | name = data.name | 
|  | safename = data.safename | 
|  | print() | 
|  | print("[[%s]]" % safename) | 
|  | print(name) | 
|  | print() | 
|  | for p in data.packages: | 
|  | package_notice = "" | 
|  | if p.licensed_files.kind == "OnlySpecificFiles": | 
|  | package_notice = " - only the following file(s):" | 
|  | elif p.licensed_files.kind == "AllFilesExceptSpecific": | 
|  | package_notice = " - except the following file(s):" | 
|  |  | 
|  | print("* " + get_package_display_name(p) + package_notice) | 
|  | for file in p.licensed_files.files: | 
|  | print("** " + file) | 
|  | print() | 
|  | print("[[%s_license]]" % safename) | 
|  | print("----") | 
|  | license_text = data.license_text | 
|  | print(data.license_text.rstrip("\r\n")) | 
|  | print() | 
|  | print("----") | 
|  | print() | 
|  |  | 
|  | if args.asciidoctor: | 
|  | print(""" | 
|  | GERRIT | 
|  | ------ | 
|  | Part of link:index.html[Gerrit Code Review] | 
|  | """) | 
|  |  | 
|  | def load_jsons(json_filenames): | 
|  | """Loads information about licenses from jsons files. | 
|  | The json files are generated by license-map-generator.ts tool | 
|  |  | 
|  | Args: | 
|  | json_filenames: list of string; each string is a filename | 
|  | Returns: | 
|  | list of LicenseMapItem | 
|  | """ | 
|  | result = [] | 
|  | for json_map in json_filenames: | 
|  | with open(json_map, 'r') as f: | 
|  | licenses_list = json.load(f) | 
|  | for license_id, license in licenses_list.items(): | 
|  | name = license["licenseName"] | 
|  | safename = name.replace(".", "_") | 
|  | packages = [] | 
|  | for p in license["packages"]: | 
|  | package = PackageInfo(name=p["name"], version=p["version"], | 
|  | licensed_files=get_licensed_files( | 
|  | p["licensedFiles"])) | 
|  | packages.append(package) | 
|  | result.append(LicenseMapItem( | 
|  | name=name, | 
|  | safename=safename, | 
|  | license_text=license["licenseText"], | 
|  | packages=sorted(remove_duplicated_packages(packages), | 
|  | key=lambda package: get_package_display_name( | 
|  | package)), | 
|  | )) | 
|  | return result | 
|  |  | 
|  | def get_licensed_files(json_licensed_file_dict): | 
|  | """Convert json dictionary to LicensedFiles""" | 
|  | kind = json_licensed_file_dict["kind"] | 
|  | if kind == "AllFiles": | 
|  | return LicensedFiles(kind="All", files=[]) | 
|  | if kind == "OnlySpecificFiles" or kind == "AllFilesExceptSpecific": | 
|  | return LicensedFiles(kind=kind, files=sorted(json_licensed_file_dict["files"])) | 
|  | raise Exception("Invalid licensed files kind: %s".format(kind)) | 
|  |  | 
|  | def get_package_display_name(package): | 
|  | """Returns a human-readable name of package with optional version""" | 
|  | if package.version: | 
|  | return package.name + " - " + package.version | 
|  | else: | 
|  | return package.name | 
|  |  | 
|  |  | 
|  | def can_merge_packages(package_info_list): | 
|  | """Returns true if all versions of a package can be replaced with | 
|  | a package name | 
|  |  | 
|  | Args: | 
|  | package_info_list: list of PackageInfo. Method assumes, | 
|  | that all items in package_info_list have the same package name, | 
|  | but different package version. | 
|  |  | 
|  | Returns: | 
|  | True if it is safe to print only a package name (without versions) | 
|  | False otherwise | 
|  | """ | 
|  | first = package_info_list[0] | 
|  | for package in package_info_list: | 
|  | if package.licensed_files != first.licensed_files: | 
|  | return False | 
|  | return True | 
|  |  | 
|  |  | 
|  | def remove_duplicated_packages(package_info_list): | 
|  | """ Keep only the name of a package if all versions of the package | 
|  | have the same licensed files. | 
|  |  | 
|  | Args: | 
|  | package_info_list: list of PackageInfo. All items in the list | 
|  | have the same license. | 
|  |  | 
|  | Returns: | 
|  | list of PackageInfo with removed/replaced items. | 
|  |  | 
|  | Keep single version of package if all versions have the same | 
|  | license files.""" | 
|  | name_to_package = defaultdict(list) | 
|  | for package in package_info_list: | 
|  | name_to_package[package.name].append(package) | 
|  |  | 
|  | result = [] | 
|  | for package_name, packages in name_to_package.items(): | 
|  | if can_merge_packages(packages): | 
|  | package = packages[0] | 
|  | result.append(PackageInfo(name=package.name, version=None, | 
|  | licensed_files=package.licensed_files)) | 
|  | else: | 
|  | result.extend(packages) | 
|  | return result | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |