blob: f0b86f1121ebb9644f603830301fb19a6f55ae10 [file] [log] [blame]
Manoj Guptad1a78462022-01-13 21:46:42 -08001# Copyright 2022 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Creates a remote_toolchain_inputs file for Reclient.
6
7Reclient(go/rbe/dev/x/reclient) is used for remote execution of build
8actions in build systems e.g. Chrome. It needs a toolchain inputs file
9next to clang compiler binary which has all the input dependencies
10needed to run the clang binary remotely.
11
12Running the script:
13$ generate_reclient_inputs [--output file_name] [--clang /path/to/clang]
14will create the file /path/to/file_name.
15
16By default, the script will write to /usr/bin/remote_toolchain_inputs.
17
18Contact: Chrome OS toolchain team.
19"""
20
21import os
22from pathlib import Path
23from typing import List, Optional, Set
24
25from chromite.lib import commandline
26from chromite.lib import cros_build_lib
27from chromite.third_party import lddtree
28
29
30def _GetSymLinkPath(base_dir: Path, link_path: str) -> Path:
31 """Return the actual symlink path relative to base directory."""
32 if not link_path:
33 return None
34 # Handle absolute symlink paths.
35 if link_path[0] == '/':
36 return link_path
37 # handle relative symlinks.
38 return base_dir / link_path
39
40
41def _CollectElfDeps(elfpath: Path) -> Set[Path]:
42 """Returns the set of dependent files for the elf file."""
43 libs = set()
44 to_process = []
45 elf = lddtree.ParseELF(elfpath, ldpaths=lddtree.LoadLdpaths())
46 for _, lib_data in elf['libs'].items():
47 if lib_data['path']:
48 to_process.append(Path(lib_data['path']))
49
50 while to_process:
51 path = to_process.pop()
52 if not path or path in libs:
53 continue
54 libs.add(path)
55 if path.is_symlink():
56 # TODO: Replace os.readlink() by path.readlink().
57 to_process.append(_GetSymLinkPath(path.parent, os.readlink(path)))
58
59 return libs
60
61
62def _GenerateRemoteInputsFile(out_file: str, clang_path: Path) -> None:
63 """Generate Remote Inputs for Clang for executing on reclient/RBE."""
64 clang_dir = clang_path.parent
65 # Start with collecting shared library dependencies.
66 paths = _CollectElfDeps(clang_path)
67
68 # Clang is typically a symlink, collect actual files.
69 paths.add(clang_path)
70 clang_file = clang_path
71 while clang_file.is_symlink():
72 clang_file = _GetSymLinkPath(clang_file.parent, os.readlink(clang_file))
73 paths.add(clang_file)
74
75 # Add clang resource directory and gcc config directory.
76 cmd = [str(clang_path), '--print-resource-dir']
77 resource_dir = cros_build_lib.run(
78 cmd, capture_output=True, encoding='utf-8',
79 print_cmd=False).stdout.splitlines()[0]
80 paths.add(Path(resource_dir) / 'share')
81 paths.add(Path('/etc/env.d/gcc'))
82
83 # Write the files relative to clang binary location.
84 with (clang_dir / out_file).open('w', encoding='utf-8') as f:
85 f.writelines(os.path.relpath(x, clang_dir) + '\n' for x in sorted(paths))
86
87
88def ParseArgs(argv: Optional[List[str]]) -> commandline.argparse.Namespace:
89 """Parses program arguments."""
90 parser = commandline.ArgumentParser(description=__doc__)
91
92 parser.add_argument(
93 '--output',
94 default='remote_toolchain_inputs',
95 help='Name of remote toolchain file relative to clang binary directory.')
96 parser.add_argument(
97 '--clang', type=Path, default='/usr/bin/clang', help='Clang binary path.')
98
99 opts = parser.parse_args(argv)
100 opts.Freeze()
101 return opts
102
103
104def main(argv: Optional[List[str]] = None) -> Optional[int]:
105 cros_build_lib.AssertInsideChroot()
106 opts = ParseArgs(argv)
107 _GenerateRemoteInputsFile(opts.output, opts.clang)