xref: /aosp_15_r20/external/cronet/third_party/abseil-cpp/generate_def_files.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env python3
2
3"""Script to generate Chromium's Abseil .def files at roll time.
4
5This script generates //third_party/abseil-app/absl/symbols_*.def at Abseil
6roll time.
7
8Since Abseil doesn't export symbols, Chromium is forced to consider all
9Abseil's symbols as publicly visible. On POSIX it is possible to use
10-fvisibility=default but on Windows a .def file with all the symbols
11is needed.
12
13Unless you are on a Windows machine, you need to set up your Chromium
14checkout for cross-compilation by following the instructions at
15https://chromium.googlesource.com/chromium/src.git/+/main/docs/win_cross.md.
16If you are on Windows, you may need to tweak this script to run, e.g. by
17changing "gn" to "gn.bat", changing "llvm-nm" to the name of your copy of
18llvm-nm, etc.
19"""
20
21import fnmatch
22import logging
23import os
24import re
25import subprocess
26import sys
27import tempfile
28import time
29
30# Matches mangled symbols containing 'absl' or starting with 'Absl'. This is
31# a good enough heuristic to select Abseil symbols to list in the .def file.
32# See https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names,
33# which describes decorations under different calling conventions. We mostly
34# just attempt to handle any leading underscore for C names (as in __cdecl).
35ABSL_SYM_RE = r'0* [BT] (?P<symbol>[?]+[^?].*absl.*|_?Absl.*)'
36if sys.platform == 'win32':
37  # Typical dumpbin /symbol lines look like this:
38  # 04B 0000000C SECT14 notype       Static       | ?$S1@?1??SetCurrent
39  # ThreadIdentity@base_internal@absl@@YAXPAUThreadIdentity@12@P6AXPAX@Z@Z@4IA
40  #  (unsigned int `void __cdecl absl::base_internal::SetCurrentThreadIdentity...
41  # We need to start on "| ?" and end on the first " (" (stopping on space would
42  # also work).
43  # This regex is identical inside the () characters except for the ? after .*,
44  # which is needed to prevent greedily grabbing the undecorated version of the
45  # symbols.
46  ABSL_SYM_RE = r'.*External     \| (?P<symbol>[?]+[^?].*?absl.*?|_?Absl.*?)($| \(.*)'
47  # Typical exported symbols in dumpbin /directives look like:
48  #    /EXPORT:?kHexChar@numbers_internal@absl@@3QBDB,DATA
49  ABSL_EXPORTED_RE = r'.*/EXPORT:(.*),.*'
50
51
52def _DebugOrRelease(is_debug):
53  return 'dbg' if is_debug else 'rel'
54
55
56def _GenerateDefFile(cpu, is_debug, extra_gn_args=[], suffix=None):
57  """Generates a .def file for the absl component build on the specified CPU."""
58  if extra_gn_args:
59    assert suffix != None, 'suffix is needed when extra_gn_args is used'
60
61  flavor = _DebugOrRelease(is_debug)
62  gn_args = [
63      'ffmpeg_branding = "Chrome"',
64      'is_component_build = true',
65      'is_debug = {}'.format(str(is_debug).lower()),
66      'proprietary_codecs = true',
67      'symbol_level = 0',
68      'target_cpu = "{}"'.format(cpu),
69      'target_os = "win"',
70  ]
71  gn_args.extend(extra_gn_args)
72
73  gn = 'gn'
74  autoninja = 'autoninja'
75  symbol_dumper = ['third_party/llvm-build/Release+Asserts/bin/llvm-nm']
76  if sys.platform == 'win32':
77    gn = 'gn.bat'
78    autoninja = 'autoninja.bat'
79    symbol_dumper = ['dumpbin', '/symbols']
80    import shutil
81    if not shutil.which('dumpbin'):
82      logging.error('dumpbin not found. Run tools\win\setenv.bat.')
83      exit(1)
84  with tempfile.TemporaryDirectory() as out_dir:
85    logging.info('[%s - %s] Creating tmp out dir in %s', cpu, flavor, out_dir)
86    subprocess.check_call([gn, 'gen', out_dir, '--args=' + ' '.join(gn_args)],
87                          cwd=os.getcwd())
88    logging.info('[%s - %s] gn gen completed', cpu, flavor)
89    subprocess.check_call(
90        [autoninja, '-C', out_dir, 'third_party/abseil-cpp:absl_component_deps'],
91        cwd=os.getcwd())
92    logging.info('[%s - %s] autoninja completed', cpu, flavor)
93
94    obj_files = []
95    for root, _dirnames, filenames in os.walk(
96        os.path.join(out_dir, 'obj', 'third_party', 'abseil-cpp')):
97      matched_files = fnmatch.filter(filenames, '*.obj')
98      obj_files.extend((os.path.join(root, f) for f in matched_files))
99
100    logging.info('[%s - %s] Found %d object files.', cpu, flavor, len(obj_files))
101
102    absl_symbols = set()
103    dll_exports = set()
104    if sys.platform == 'win32':
105      for f in obj_files:
106        # Track all of the functions exported with __declspec(dllexport) and
107        # don't list them in the .def file - double-exports are not allowed. The
108        # error is "lld-link: error: duplicate /export option".
109        exports_out = subprocess.check_output(['dumpbin', '/directives', f], cwd=os.getcwd())
110        for line in exports_out.splitlines():
111          line = line.decode('utf-8')
112          match = re.match(ABSL_EXPORTED_RE, line)
113          if match:
114            dll_exports.add(match.groups()[0])
115    for f in obj_files:
116      stdout = subprocess.check_output(symbol_dumper + [f], cwd=os.getcwd())
117      for line in stdout.splitlines():
118        try:
119          line = line.decode('utf-8')
120        except UnicodeDecodeError:
121          # Due to a dumpbin bug there are sometimes invalid utf-8 characters in
122          # the output. This only happens on an unimportant line so it can
123          # safely and silently be skipped.
124          # https://developercommunity.visualstudio.com/content/problem/1091330/dumpbin-symbols-produces-randomly-wrong-output-on.html
125          continue
126        match = re.match(ABSL_SYM_RE, line)
127        if match:
128          symbol = match.group('symbol')
129          assert symbol.count(' ') == 0, ('Regex matched too much, probably got '
130                                          'undecorated name as well')
131          # Avoid getting names exported with dllexport, to avoid
132          # "lld-link: error: duplicate /export option" on symbols such as:
133          # ?kHexChar@numbers_internal@absl@@3QBDB
134          if symbol in dll_exports:
135            continue
136          # Avoid to export deleting dtors since they trigger
137          # "lld-link: error: export of deleting dtor" linker errors, see
138          # crbug.com/1201277.
139          if symbol.startswith('??_G'):
140            continue
141          # Strip any leading underscore for C names (as in __cdecl). It's only
142          # there on x86, but the x86 toolchain falls over when you include it!
143          if cpu == 'x86' and symbol.startswith('_'):
144            symbol = symbol[1:]
145          absl_symbols.add(symbol)
146
147    logging.info('[%s - %s] Found %d absl symbols.', cpu, flavor, len(absl_symbols))
148
149    if extra_gn_args:
150      def_file = os.path.join('third_party', 'abseil-cpp',
151                              'symbols_{}_{}_{}.def'.format(cpu, flavor, suffix))
152    else:
153      def_file = os.path.join('third_party', 'abseil-cpp',
154                             'symbols_{}_{}.def'.format(cpu, flavor))
155
156    with open(def_file, 'w', newline='') as f:
157      f.write('EXPORTS\n')
158      for s in sorted(absl_symbols):
159        f.write('    {}\n'.format(s))
160
161    # Hack, it looks like there is a race in the directory cleanup.
162    time.sleep(10)
163
164  logging.info('[%s - %s] .def file successfully generated.', cpu, flavor)
165
166
167if __name__ == '__main__':
168  logging.getLogger().setLevel(logging.INFO)
169
170  if sys.version_info.major == 2:
171    logging.error('This script requires Python 3.')
172    exit(1)
173
174  if not os.getcwd().endswith('src') or not os.path.exists('chrome/browser'):
175    logging.error('Run this script from a chromium/src/ directory.')
176    exit(1)
177
178  _GenerateDefFile('x86', True)
179  _GenerateDefFile('x86', False)
180  _GenerateDefFile('x64', True)
181  _GenerateDefFile('x64', False)
182  _GenerateDefFile('x64', False, ['is_asan = true'], 'asan')
183  _GenerateDefFile('arm64', True)
184  _GenerateDefFile('arm64', False)
185