| #!/usr/bin/env python3 |
| # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Build the project's dependencies.""" |
| |
| import html |
| import json |
| import logging |
| import os |
| import re |
| import sys |
| |
| import nassh |
| import libdot |
| |
| |
| LICENSE_FILE = os.path.join(nassh.DIR, 'html', 'licenses.html') |
| LICENSE_TEMPLATE = os.path.join(nassh.DIR, 'html', 'licenses.html.in') |
| |
| # Make sure we keep a handle on what licenses we ship. This is not the list of |
| # licenses that are actually used, just all the ones that we're OK with using. |
| ALLOWED_LICENSES = { |
| 'Apache-2.0', |
| 'BSD', |
| 'BSD-2-Clause', |
| 'BSD-3-Clause', |
| 'CC0', |
| 'GPL-2', |
| 'GPL-2.0', |
| 'GPL-3', |
| 'GPL-3.0', |
| 'ISC', |
| 'LGPL', |
| 'LGPL-2.1', |
| 'LGPL-3', |
| 'MIT', |
| 'Unlicense', |
| 'zlib', |
| } |
| |
| # Licenses that we explicitly never want to use. Do *not* remove any of these. |
| BANNED_LICENSES = { |
| 'AGPL', |
| 'AGPL-3', |
| 'AGPL-3+', |
| 'CC-BY-NC', |
| 'CC-BY-NC-ND', |
| 'CC-BY-NC-SA', |
| 'CPAL', |
| 'EUPL', |
| 'SISSL', |
| 'SSPL', |
| 'WTFPL', |
| } |
| assert not BANNED_LICENSES & ALLOWED_LICENSES |
| |
| |
| def mkdeps(rollup=True): |
| """Build the dependencies.""" |
| libdot.concat.concat(os.path.join(nassh.DIR, 'concat', 'nassh_deps.concat'), |
| os.path.join(nassh.DIR, 'js', 'nassh_deps.concat.js')) |
| if rollup: |
| libdot.node.run(['rollup', '-c'], cwd=nassh.DIR) |
| |
| |
| def concat_third_party_dir(third_party_dir): |
| """Concatenate all licenses of |third_party_dir| bundles.""" |
| ret = {} |
| for package in os.listdir(third_party_dir): |
| entry = {} |
| version = None |
| |
| package_dir = os.path.join(third_party_dir, package) |
| |
| metadata_file = os.path.join(package_dir, 'METADATA') |
| with open(metadata_file, 'r', encoding='utf-8') as fp: |
| lines = fp.readlines() |
| for i, line in enumerate(lines): |
| if 'HOMEPAGE' in line: |
| m = re.match(r'.*"(.*)"', lines[i + 1]) |
| entry['repository'] = m.group(1) |
| elif 'version' in line: |
| m = re.match(r'.*"(.*)"', line) |
| version = m.group(1) |
| |
| for path in ('LICENSE', 'LICENSE.md'): |
| license_file = os.path.join(package_dir, path) |
| if not os.path.exists(license_file): |
| continue |
| logging.debug('%s: loading %s', package, license_file) |
| with open(license_file, 'r', encoding='utf-8') as fp: |
| entry['data'] = fp.read().strip() |
| break |
| else: |
| raise ValueError('Unable to locate LICENSE for %s' % (package,)) |
| |
| ret['%s@%s' % (package, version)] = entry |
| |
| return ret |
| |
| |
| def concat_local_deps(): |
| """Concatenate all licenses of third_party/ bundles.""" |
| ret = {} |
| ret.update(concat_third_party_dir(os.path.join(nassh.DIR, 'third_party'))) |
| ret.update(concat_third_party_dir( |
| os.path.join(libdot.LIBAPPS_DIR, 'ssh_client', 'third_party'))) |
| return ret |
| |
| |
| def concat_licenses(): |
| """Concatenate all licenses of npm dependencies.""" |
| ret = libdot.node.run([ |
| 'license-checker', '--search', nassh.DIR, '--onlyunknown', |
| '--production', '--csv', |
| ], capture_output=True, cwd=nassh.DIR) |
| # 'Found error' in stderr indicates that no packages with unspecified |
| # licenses were found. |
| if b'Found error' in ret.stderr: |
| ret = libdot.node.run([ |
| 'license-checker', '--search', nassh.DIR, '--unknown', |
| '--production', '--json', '--onlyAllow', |
| ';'.join(sorted(ALLOWED_LICENSES)), |
| ], capture_output=True, cwd=nassh.DIR) |
| res = json.loads(ret.stdout.decode('utf8')) |
| for entry in res.values(): |
| with open(entry['licenseFile'], 'r', encoding='utf8') as fp: |
| entry['data'] = fp.read().strip() |
| |
| res.update(concat_local_deps()) |
| |
| generate_html(res) |
| else: |
| logging.error('The following packages did not specify their licenses:') |
| logging.error(ret.stdout.decode('utf8')) |
| |
| |
| # Template for every package/license entry. |
| LICENSE_ENTRY_TEMPLATE = """ |
| <h2 class='package' id='package-%(package)s'> |
| <a href='%(repository)s'>%(package)s</a> |
| <a class='license' |
| id='show-%(package)s' |
| i18n='{"aria-label": "$id", "_": "LICENSES_EXPAND_LINK"}' |
| href='#'></a> |
| </h2> |
| <pre class='license' id='license-%(package)s'> |
| %(data)s |
| </pre> |
| """ |
| |
| def generate_html(licenses): |
| """Write the collected |licenses| to an HTML file.""" |
| with open(LICENSE_TEMPLATE, 'r', encoding='utf8') as fp: |
| template = fp.read() |
| output = '' |
| for package in sorted(licenses): |
| entry = licenses[package] |
| output += LICENSE_ENTRY_TEMPLATE % { |
| 'repository': entry['repository'], |
| 'package': html.escape(package), |
| 'data': html.escape(entry['data']), |
| } |
| output = template.replace('%%LICENSES%%', output) |
| with open(LICENSE_FILE, 'w', encoding='utf8') as fp: |
| fp.write(output) |
| |
| |
| def get_parser(): |
| """Get a command line parser.""" |
| parser = libdot.ArgumentParser(description=__doc__) |
| parser.add_argument('--skip-rollup', dest='rollup', |
| action='store_false', default=True, |
| help='Skip (re)building of rollup dependencies.') |
| return parser |
| |
| |
| def main(argv): |
| """The main func!""" |
| parser = get_parser() |
| opts = parser.parse_args(argv) |
| libdot.node_and_npm_setup() |
| nassh.fonts.fonts_update() |
| mkdeps(rollup=opts.rollup) |
| nassh.generate_changelog.generate_changelog() |
| |
| if opts.rollup: |
| logging.info('Concatenating licenses...') |
| # We use nassh's package.json, but reuse libapps' node_modules. |
| libdot.symlink( |
| os.path.join(libdot.LIBAPPS_DIR, 'node_modules'), |
| os.path.join(nassh.DIR, 'node_modules')) |
| try: |
| concat_licenses() |
| finally: |
| libdot.unlink(os.path.join(nassh.DIR, 'node_modules')) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |