blob: 02dcdeaf97ca8f86211d1d4ac770434c497ed47a [file] [log] [blame]
#!/usr/bin/env vpython3
#
# Copyright 2019 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.
"""
Use this script to update node_modules instead of
running npm install manually. To upgrade a dependency, change the version
number in package.json below and run this script, which can be done with `npm run
install-deps` locally.
"""
import os
import os.path as path
import json
import subprocess
import sys
from collections import OrderedDict
scripts_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(scripts_path)
import devtools_paths
LICENSES = [
"MIT",
"Apache-2.0",
"BSD",
"BSD-2-Clause",
"BSD-3-Clause",
"CC0-1.0",
"CC-BY-3.0",
"CC-BY-4.0",
"ISC",
"MPL-2.0",
"Python-2.0",
"W3C",
]
def load_json_file(location):
# By default, json load uses a standard Python dictionary, which is not ordered.
# To prevent subsequent invocations of this script to erroneously alter the order
# of keys defined in package.json files, we should use an `OrderedDict`. This
# ensures not only that we use a strict ordering, it will also make sure we maintain
# the order defined by the NPM packages themselves. That in turn is important, since
# NPM packages can define `exports`, where the order of entrypoints is crucial for
# how an NPM package is loaded. If you would change the order, it could break loading
# that package.
return json.load(location, object_pairs_hook=OrderedDict)
DEPS = {}
pkg_file = path.join(devtools_paths.devtools_root_path(), 'package.json')
with open(pkg_file, 'r+') as pkg_file:
DEPS = load_json_file(pkg_file)["devDependencies"]
def exec_command(cmd):
try:
new_env = os.environ.copy()
cmd_proc_result = subprocess.check_call(
cmd, cwd=devtools_paths.devtools_root_path(), env=new_env)
except Exception as error:
print(error)
return True
return False
def ensure_licenses():
cmd = [
devtools_paths.node_path(),
devtools_paths.license_checker_path(), '--onlyAllow',
('%s' % (';'.join(LICENSES)))
]
return exec_command(cmd)
def strip_private_fields():
# npm adds private fields which need to be stripped.
packages = []
for root, _, filenames in os.walk(devtools_paths.node_modules_path()):
if 'package.json' in filenames:
packages.append(path.join(root, 'package.json'))
for pkg in packages:
with open(pkg, 'r+') as pkg_file:
try:
pkg_data = load_json_file(pkg_file)
updated_pkg_data = pkg_data.copy()
# Remove anything that begins with an underscore, as these are
# the private fields in a package.json
for key in pkg_data.keys():
if key.find('_') == 0:
updated_pkg_data.pop(key)
pkg_file.truncate(0)
pkg_file.seek(0)
json.dump(updated_pkg_data,
pkg_file,
indent=2,
separators=(',', ': '))
pkg_file.write('\n')
except:
print('Unable to fix: %s, %s' % (pkg, sys.exc_info()))
return True
return False
def remove_package_json_entries():
with open(devtools_paths.package_json_path(), 'r+') as pkg_file:
try:
pkg_data = load_json_file(pkg_file)
# Remove the dependencies and devDependencies from the root package.json
# so that they can't be used to overwrite the node_modules managed by this file.
for key in pkg_data.keys():
if key.find('dependencies') == 0:
pkg_data.pop(key)
pkg_file.truncate(0)
pkg_file.seek(0)
json.dump(pkg_data, pkg_file, indent=2, separators=(',', ': '))
pkg_file.write('\n')
except:
print('Unable to fix: %s' % pkg)
return True
return False
def addClangFormat():
with open(path.join(devtools_paths.node_modules_path(), '.clang-format'),
'w+') as clang_format_file:
try:
clang_format_file.write('DisableFormat: true\n')
except:
print('Unable to write .clang-format file')
return True
return False
def addOwnersFile():
with open(path.join(devtools_paths.node_modules_path(), 'OWNERS'),
'w+') as owners_file:
try:
owners_file.write('file://config/owner/INFRA_OWNERS\n')
except:
print('Unable to write OWNERS file')
return True
return False
def addChromiumReadme():
with open(path.join(devtools_paths.node_modules_path(), 'README.chromium'),
'w+') as readme_file:
try:
readme_file.write(
'This directory hosts all packages downloaded from NPM that are used in either the build system or infrastructure scripts.\n'
)
readme_file.write(
'If you want to make any changes to this directory, please see "scripts/deps/manage_node_deps.py".\n'
)
except:
print('Unable to write README.chromium file')
return True
return False
def run_npm_command():
for (name, version) in DEPS.items():
if (version.find('^') == 0):
print(
'Versions must be locked to a specific version; remove ^ from the start of the version.'
)
return True
# Modern npm versions do not cause extra updates so it is not necessary to run clean install.
if exec_command([
'npm',
'install',
]):
return True
# To minimize disk usage for Chrome DevTools node_modules, always try to dedupe dependencies.
# We need to perform this every time, as the order of dependencies added could lead to a
# non-optimal dependency tree, resulting in unnecessary disk usage.
if exec_command([
'npm',
'dedupe',
]):
return True
if remove_package_json_entries():
return True
if strip_private_fields():
return True
if addClangFormat():
return True
if addOwnersFile():
return True
if addChromiumReadme():
return True
return ensure_licenses()
npm_errors_found = run_npm_command()
if npm_errors_found:
print('npm command failed')
exit(1)