| # 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_idl', |
| 'updater_internal_idl_idl', |
| 'updater_legacy_idl_idl', |
| '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() |