| #!/usr/bin/env vpython |
| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Checks go package imports to ensure they're not too wacky.""" |
| |
| from __future__ import absolute_import |
| from __future__ import print_function |
| import os |
| import subprocess |
| import sys |
| import urlparse |
| |
| import yaml |
| |
| |
| # This lists the go packages to scan. Can be any package pattern accepted |
| # by `go list` |
| PKGS_TO_CHECK = [ |
| 'infra/...', |
| 'go.chromium.org/...', |
| ] |
| |
| |
| # We have imports which only occur in some OS-specific .go files, so run |
| # `go list` under these $GOOS values to catch 'em all. |
| GOOS_TO_CHECK = ( |
| 'darwin', |
| 'linux', |
| 'windows', |
| ) |
| |
| SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__)) |
| |
| DEVNULL = open(os.devnull, 'wb') |
| |
| |
| def scan_os_async(goos): |
| env = os.environ.copy() |
| env['GOOS'] = goos |
| |
| FMT = ( |
| '{{$pkg := .ImportPath}}' |
| '{{range .Imports}}' |
| '{{printf "%s %s\\n" $pkg .}}' |
| '{{end}}' |
| '{{range .TestImports}}' |
| '{{printf "%s %s\\n" $pkg .}}' |
| '{{end}}' |
| ) |
| p = subprocess.Popen( |
| ['go', 'list', '-f', FMT] + PKGS_TO_CHECK, env=env, |
| stdout=subprocess.PIPE, stderr=DEVNULL) |
| return lambda: p.communicate()[0] |
| |
| |
| def read_and_merge(*go_list_data_func): |
| ret = {} |
| for fn in go_list_data_func: |
| for line in fn().splitlines(): |
| pkgname, import_pkg = line.split() |
| ret.setdefault(import_pkg, set()).add(pkgname) |
| return ret |
| |
| |
| def apply_allowlist(import_to_users, allowlist): |
| used_allowlist = set() |
| |
| def matches(imp): |
| for x in allowlist: |
| if imp.startswith(x): |
| used_allowlist.add(x) |
| return True |
| return False |
| |
| for imp in import_to_users.keys(): |
| if matches(imp): |
| del import_to_users[imp] |
| continue |
| |
| # Does it look like stdlib? |
| if '.' not in imp.split('/')[0]: |
| del import_to_users[imp] |
| continue |
| |
| return allowlist - used_allowlist |
| |
| |
| def load_allowlist(path): |
| ret = set() |
| with open(path, 'r') as f: |
| for line in f.readlines(): |
| line = line.strip() |
| if not line or line.startswith('#'): |
| continue |
| ret.add(line) |
| return set(ret) |
| |
| |
| def check_all_googlesource_mirrors(): |
| ret = [] |
| |
| with open(os.path.join(SCRIPT_DIR, 'deps.lock')) as f: |
| lock_file = yaml.safe_load(f) |
| |
| bad_urls = {} |
| for imp in lock_file['imports']: |
| repo = urlparse.urlparse(imp['repo']) |
| if not repo.netloc.endswith('.googlesource.com'): |
| bad_urls[imp['name']] = '%s/%s' % (repo.scheme, repo.netloc) |
| |
| if bad_urls: |
| ret.append('Non-googlesource.com dep repos:') |
| for name, url in sorted(bad_urls.items()): |
| ret.append(' %s: %s' % (name, url)) |
| ret.append('') |
| |
| return ret |
| |
| |
| def check_only_allowlisted_deps(): |
| ret = [] |
| |
| allowlist = load_allowlist(os.path.join(SCRIPT_DIR, 'check_deps.allowlist')) |
| import_to_users = read_and_merge(*map(scan_os_async, GOOS_TO_CHECK)) |
| |
| unused_allowlist = apply_allowlist(import_to_users, allowlist) |
| |
| if import_to_users: |
| pkg_to_bad_imports = {} |
| for bad_import, pkg_users in sorted(import_to_users.items()): |
| for pkg in pkg_users: |
| pkg_to_bad_imports.setdefault(pkg, []).append(bad_import) |
| |
| ret.append('Invalid Go package dependencies:') |
| for i, (pkg, bad_imports) in enumerate(sorted(pkg_to_bad_imports.items())): |
| if i != 0: |
| ret.append('') |
| ret.append(' in %s' % pkg) |
| for imp in bad_imports: |
| ret.append(' - %s' % imp) |
| ret.append('') |
| |
| if unused_allowlist: |
| ret.append('Unused allowlist entries:') |
| for entry in sorted(unused_allowlist): |
| ret.append(' %s' % entry) |
| ret.append('') |
| |
| return ret |
| |
| |
| def main(): |
| lines = [] |
| lines += check_all_googlesource_mirrors() |
| lines += check_only_allowlisted_deps() |
| |
| for l in lines: |
| print(l) |
| |
| if lines: |
| sys.exit(1) |
| |
| |
| if __name__ == '__main__': |
| main() |