# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A tool for updating IDL COM headers/TLB after updating IDL template.
This tool must be run from a Windows machine at the source root directory.
Example:
python3 tools/win/update_idl.py
"""
import os
import platform
import subprocess
class IDLUpdateError(Exception):
"""Module exception class."""
class IDLUpdater:
"""A class to update IDL COM headers/TLB files based on config."""
def __init__(self, idl_gn_target: str, target_cpu: str,
is_chrome_branded: bool):
self.idl_gn_target = idl_gn_target
self.target_cpu = target_cpu
self.is_chrome_branded = str(is_chrome_branded).lower()
self.output_dir = r'out\idl_update'
def update(self) -> None:
print('Updating', self.idl_gn_target, 'IDL files for', self.target_cpu,
'CPU, chrome_branded:', self.is_chrome_branded, '...')
self._make_output_dir()
self._gen_gn_args()
self._autoninja_and_update()
def _make_output_dir(self) -> None:
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
def _gen_gn_args(self) -> None:
# If the gn_args file already exists and has the desired values then
# don't touch it - this avoids unnecessary and expensive gn gen
# invocations.
gn_args_path = os.path.join(self.output_dir, 'args.gn')
contents = (f'target_cpu="{self.target_cpu}"\n'
f'is_chrome_branded={self.is_chrome_branded}\n'
f'is_debug=true\n'
f'enable_nacl=false\n'
f'blink_symbol_level=0\n'
f'v8_symbol_level=0\n').format()
if os.path.exists(gn_args_path):
with open(gn_args_path, 'rt', newline='') as f:
new_contents = f.read()
if new_contents == contents:
return
# `subprocess` may interpret the complex config values passed via
# `--args` differently than intended. Generate the default gn.args first
# and then update it by writing directly.
# gen args with default values.
print('Generating', gn_args_path, 'with default values.')
subprocess.run(['gn.bat', 'gen', self.output_dir], check=True)
# Manually update args.gn
print('Write', gn_args_path, 'with desired config.')
with open(gn_args_path, 'wt', newline='') as f:
f.write(contents)
print('Done.')
def _autoninja_and_update(self) -> None:
print('Check if update is needed by building the target...')
# Use -j 1 since otherwise the exact build output is not deterministic.
proc = subprocess.run([
'autoninja.bat', '-j', '1', '-C', self.output_dir,
self.idl_gn_target
],
capture_output=True,
check=False,
universal_newlines=True)
if proc.returncode == 0:
print('No update is needed.\n')
return
cmd = self._extract_update_command(proc.stdout)
print('Updating IDL COM headers/TLB by running: [', cmd, ']...')
subprocess.run(cmd, shell=True, capture_output=True, check=True)
print('Done.\n')
def _extract_update_command(self, stdout: str) -> str:
# Exclude blank lines.
lines = list(filter(None, stdout.splitlines()))
if (len(lines) < 3
or 'ninja: build stopped: subcommand failed.' not in lines[-1]
or 'copy /y' not in lines[-2]
or 'To rebaseline:' not in lines[-3]):
print('-' * 80)
print('STDOUT:')
print(stdout)
print('-' * 80)
raise IDLUpdateError(
'Unexpected autoninja error, or update this tool if the output '
'format is changed.')
return lines[-2].strip().replace('..\\..\\', '')
def check_running_environment() -> None:
if 'Windows' not in platform.system():
raise IDLUpdateError('This tool must run from Windows platform.')
proc = subprocess.run(['git.bat', 'rev-parse', '--show-toplevel'],
capture_output=True,
check=True)
if proc.returncode != 0:
raise IDLUpdateError(
'Failed to run git for finding source root directory.')
source_root = os.path.abspath(proc.stdout.decode('utf-8').strip()).lower()
if not os.path.exists(source_root):
raise IDLUpdateError('Unexpected failure to get source root directory')
cwd = os.getcwd().lower()
if cwd != source_root:
raise IDLUpdateError(f'This tool must run from project root folder. '
f'CWD: [{cwd}] vs ACTUAL:[{source_root}]')
# Build performance output interferes with error parsing. Silence it.
os.environ['NINJA_SUMMARIZE_BUILD'] = '0'
def main():
check_running_environment()
for target_cpu in ['arm64', 'x64', 'x86']:
for idl_target in [
'updater_idl',
'updater_idl_user',
'updater_idl_system',
'updater_internal_idl',
'updater_internal_idl_user',
'updater_internal_idl_system',
'updater_legacy_idl',
'updater_legacy_idl_user',
'updater_legacy_idl_system',
'google_update',
'elevation_service_idl',
'gaia_credential_provider_idl',
'iaccessible2',
'ichromeaccessible',
'isimpledom',
'remoting_lib_idl',
]:
IDLUpdater(idl_target + '_idl_action', target_cpu, False).update()
if __name__ == '__main__':
main()