1*f578df4fSJingwen Chen#!/usr/bin/env python3 2*f578df4fSJingwen Chen# Copyright 2020 Google LLC 3*f578df4fSJingwen Chen# 4*f578df4fSJingwen Chen# Licensed under the Apache License, Version 2.0 (the "License"); 5*f578df4fSJingwen Chen# you may not use this file except in compliance with the License. 6*f578df4fSJingwen Chen# You may obtain a copy of the License at 7*f578df4fSJingwen Chen# 8*f578df4fSJingwen Chen# https://www.apache.org/licenses/LICENSE-2.0 9*f578df4fSJingwen Chen# 10*f578df4fSJingwen Chen# Unless required by applicable law or agreed to in writing, software 11*f578df4fSJingwen Chen# distributed under the License is distributed on an "AS IS" BASIS, 12*f578df4fSJingwen Chen# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*f578df4fSJingwen Chen# See the License for the specific language governing permissions and 14*f578df4fSJingwen Chen# limitations under the License. 15*f578df4fSJingwen Chen 16*f578df4fSJingwen Chen"""Proof of concept license checker. 17*f578df4fSJingwen Chen 18*f578df4fSJingwen ChenThis is only a demonstration. It will be replaced with other tools. 19*f578df4fSJingwen Chen""" 20*f578df4fSJingwen Chen 21*f578df4fSJingwen Chenimport argparse 22*f578df4fSJingwen Chenimport codecs 23*f578df4fSJingwen Chenimport json 24*f578df4fSJingwen Chen 25*f578df4fSJingwen Chen# Conditions allowed for all applications 26*f578df4fSJingwen Chen_ALWAYS_ALLOWED_CONDITIONS = frozenset(['notice', 'permissive', 'unencumbered']) 27*f578df4fSJingwen Chen 28*f578df4fSJingwen Chen 29*f578df4fSJingwen Chendef _load_license_data(licenses_info): 30*f578df4fSJingwen Chen with codecs.open(licenses_info, encoding='utf-8') as licenses_file: 31*f578df4fSJingwen Chen return json.loads(licenses_file.read()) 32*f578df4fSJingwen Chen 33*f578df4fSJingwen Chen 34*f578df4fSJingwen Chendef unique_licenses(licenses): 35*f578df4fSJingwen Chen for target in licenses: 36*f578df4fSJingwen Chen for lic in target.get('licenses') or []: 37*f578df4fSJingwen Chen yield lic 38*f578df4fSJingwen Chen 39*f578df4fSJingwen Chendef _do_report(out, licenses): 40*f578df4fSJingwen Chen """Produce a report showing the set of licenses being used. 41*f578df4fSJingwen Chen 42*f578df4fSJingwen Chen Args: 43*f578df4fSJingwen Chen out: file object to write to 44*f578df4fSJingwen Chen licenses: list of LicenseInfo objects 45*f578df4fSJingwen Chen 46*f578df4fSJingwen Chen Returns: 47*f578df4fSJingwen Chen 0 for no restricted licenses. 48*f578df4fSJingwen Chen """ 49*f578df4fSJingwen Chen 50*f578df4fSJingwen Chen for target in unique_licenses(licenses): 51*f578df4fSJingwen Chen for lic in target.get('licenses') or []: 52*f578df4fSJingwen Chen print("lic:", lic) 53*f578df4fSJingwen Chen rule = lic['rule'] 54*f578df4fSJingwen Chen for kind in lic['license_kinds']: 55*f578df4fSJingwen Chen out.write('= %s\n kind: %s\n' % (rule, kind['target'])) 56*f578df4fSJingwen Chen out.write(' conditions: %s\n' % kind['conditions']) 57*f578df4fSJingwen Chen 58*f578df4fSJingwen Chen 59*f578df4fSJingwen Chendef _check_conditions(out, licenses, allowed_conditions): 60*f578df4fSJingwen Chen """Check that the application does not use any disallowed licenses. 61*f578df4fSJingwen Chen 62*f578df4fSJingwen Chen Args: 63*f578df4fSJingwen Chen out: file object to write to 64*f578df4fSJingwen Chen licenses: list of LicenseInfo objects 65*f578df4fSJingwen Chen allowed_conditions: list of allowed condition names 66*f578df4fSJingwen Chen 67*f578df4fSJingwen Chen Returns: 68*f578df4fSJingwen Chen 0 for no licenses from outside allowed_conditions. 69*f578df4fSJingwen Chen """ 70*f578df4fSJingwen Chen err = 0 71*f578df4fSJingwen Chen for lic in licenses: # using strange name lic because license is built-in 72*f578df4fSJingwen Chen rule = lic['rule'] 73*f578df4fSJingwen Chen for kind in lic['license_kinds']: 74*f578df4fSJingwen Chen disallowed = [] 75*f578df4fSJingwen Chen for condition in kind['conditions']: 76*f578df4fSJingwen Chen if condition not in allowed_conditions: 77*f578df4fSJingwen Chen disallowed.append(condition) 78*f578df4fSJingwen Chen if disallowed: 79*f578df4fSJingwen Chen out.write('ERROR: %s\n' % rule) 80*f578df4fSJingwen Chen out.write(' kind: %s\n' % kind['target']) 81*f578df4fSJingwen Chen out.write(' conditions: %s\n' % kind['conditions']) 82*f578df4fSJingwen Chen out.write(' disallowed condition: %s\n' % ','.join(disallowed)) 83*f578df4fSJingwen Chen err += 1 84*f578df4fSJingwen Chen return err 85*f578df4fSJingwen Chen 86*f578df4fSJingwen Chen 87*f578df4fSJingwen Chendef _do_copyright_notices(out, licenses): 88*f578df4fSJingwen Chen for l in licenses: 89*f578df4fSJingwen Chen name = l.get('package_name') or '<unknown>' 90*f578df4fSJingwen Chen if l.get('package_version'): 91*f578df4fSJingwen Chen name = name + "/" + l['package_version'] 92*f578df4fSJingwen Chen # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 93*f578df4fSJingwen Chen out.write('package(%s), copyright(%s)\n' % (name, l['copyright_notice'])) 94*f578df4fSJingwen Chen 95*f578df4fSJingwen Chen 96*f578df4fSJingwen Chendef _do_licenses(out, licenses): 97*f578df4fSJingwen Chen for lic in unique_licenses(licenses): 98*f578df4fSJingwen Chen path = lic['license_text'] 99*f578df4fSJingwen Chen with codecs.open(path, encoding='utf-8') as license_file: 100*f578df4fSJingwen Chen out.write('= %s\n' % path) 101*f578df4fSJingwen Chen out.write(license_file.read()) 102*f578df4fSJingwen Chen 103*f578df4fSJingwen Chen 104*f578df4fSJingwen Chendef main(): 105*f578df4fSJingwen Chen parser = argparse.ArgumentParser( 106*f578df4fSJingwen Chen description='Demonstraton license compliance checker') 107*f578df4fSJingwen Chen 108*f578df4fSJingwen Chen parser.add_argument('--licenses_info', 109*f578df4fSJingwen Chen help='path to JSON file containing all license data') 110*f578df4fSJingwen Chen parser.add_argument('--report', default='report', help='Summary report') 111*f578df4fSJingwen Chen parser.add_argument('--copyright_notices', 112*f578df4fSJingwen Chen help='output file of all copyright notices') 113*f578df4fSJingwen Chen parser.add_argument('--license_texts', help='output file of all license files') 114*f578df4fSJingwen Chen parser.add_argument('--check_conditions', action='store_true', 115*f578df4fSJingwen Chen help='check that the dep only includes allowed license conditions') 116*f578df4fSJingwen Chen args = parser.parse_args() 117*f578df4fSJingwen Chen 118*f578df4fSJingwen Chen license_data = _load_license_data(args.licenses_info) 119*f578df4fSJingwen Chen target = license_data[0] # we assume only one target for the demo 120*f578df4fSJingwen Chen 121*f578df4fSJingwen Chen top_level_target = target['top_level_target'] 122*f578df4fSJingwen Chen dependencies = target['dependencies'] 123*f578df4fSJingwen Chen licenses = target['licenses'] 124*f578df4fSJingwen Chen 125*f578df4fSJingwen Chen err = 0 126*f578df4fSJingwen Chen with codecs.open(args.report, mode='w', encoding='utf-8') as rpt: 127*f578df4fSJingwen Chen _do_report(rpt, licenses) 128*f578df4fSJingwen Chen if args.check_conditions: 129*f578df4fSJingwen Chen # TODO(aiuto): Read conditions from a file of allowed conditions for 130*f578df4fSJingwen Chen # a specified application deployment environment. 131*f578df4fSJingwen Chen err = _check_conditions(rpt, licenses, _ALWAYS_ALLOWED_CONDITIONS) 132*f578df4fSJingwen Chen if args.copyright_notices: 133*f578df4fSJingwen Chen with codecs.open( 134*f578df4fSJingwen Chen args.copyright_notices, mode='w', encoding='utf-8') as out: 135*f578df4fSJingwen Chen _do_copyright_notices(out, licenses) 136*f578df4fSJingwen Chen if args.license_texts: 137*f578df4fSJingwen Chen with codecs.open(args.license_texts, mode='w', encoding='utf-8') as out: 138*f578df4fSJingwen Chen _do_licenses(out, licenses) 139*f578df4fSJingwen Chen return err 140*f578df4fSJingwen Chen 141*f578df4fSJingwen Chen 142*f578df4fSJingwen Chenif __name__ == '__main__': 143*f578df4fSJingwen Chen main() 144