summaryrefslogtreecommitdiffstats
path: root/chromium/tools/win/update_idl.py
blob: b6f6f3086c1842f2e46c146f8425a8f566674686 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# 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, proc.stderr)
        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, stderr: str) -> str:
        # Exclude blank lines.
        lines = list(filter(None, stdout.splitlines()))

        if (len(lines) < 3 or 'build stopped' 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)
            print('STDERR:')
            print(stderr)
            print('-' * 80)
            print('LINE:')
            print(lines[-1])
            print(lines[-2])
            print(lines[-3])

            raise IDLUpdateError(
                'Unexpected autoninja error (see output above). Update this '
                'tool if the output format has 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 [
                'chrome/windows_services/elevated_tracing_service:' + \
                'tracing_service_idl',
                'chrome/windows_services/service_program:test_service_idl',
                'elevation_service_idl',
                'gaia_credential_provider_idl',
                'iaccessible2',
                'ichromeaccessible',
                'isimpledom',
                'remoting_lib_idl',
                'updater_idl',
                'updater_idl_system',
                'updater_idl_user',
                'updater_internal_idl',
                'updater_internal_idl_system',
                'updater_internal_idl_user',
                'updater_legacy_idl',
                'updater_legacy_idl_system',
                'updater_legacy_idl_user',
        ]:
            IDLUpdater(idl_target + '_idl_action', target_cpu, False).update()


if __name__ == '__main__':
    main()