OSDN Git Service

Revert "Clear identity bit when passing address to filter"
[android-x86/system-bt.git] / build.py
1 #!/usr/bin/env python3
2
3 #  Copyright 2021 Google, Inc.
4 #
5 #  Licensed under the Apache License, Version 2.0 (the "License");
6 #  you may not use this file except in compliance with the License.
7 #  You may obtain a copy of the License at:
8 #
9 #  http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #  Unless required by applicable law or agreed to in writing, software
12 #  distributed under the License is distributed on an "AS IS" BASIS,
13 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #  See the License for the specific language governing permissions and
15 #  limitations under the License.
16 """ Build BT targets on the host system.
17
18 For building, you will first have to stage a platform directory that has the
19 following structure:
20 |-common-mk
21 |-bt
22 |-external
23 |-|-rust
24 |-|-|-vendor
25
26 The simplest way to do this is to check out platform2 to another directory (that
27 is not a subdir of this bt directory), symlink bt there and symlink the rust
28 vendor repository as well.
29 """
30 import argparse
31 import multiprocessing
32 import os
33 import shutil
34 import six
35 import subprocess
36 import sys
37
38 # Use flags required by common-mk (find -type f | grep -nE 'use[.]' {})
39 COMMON_MK_USES = [
40     'asan',
41     'coverage',
42     'cros_host',
43     'fuzzer',
44     'fuzzer',
45     'msan',
46     'profiling',
47     'tcmalloc',
48     'test',
49     'ubsan',
50 ]
51
52 # Default use flags.
53 USE_DEFAULTS = {
54     'android': False,
55     'bt_nonstandard_codecs': False,
56     'test': False,
57 }
58
59 VALID_TARGETS = [
60     'prepare',  # Prepare the output directory (gn gen + rust setup)
61     'tools',  # Build the host tools (i.e. packetgen)
62     'rust',  # Build only the rust components + copy artifacts to output dir
63     'main',  # Build the main C++ codebase
64     'test',  # Run the unit tests
65     'clean',  # Clean up output directory
66     'all',  # All targets except test and clean
67 ]
68
69 HOST_TESTS = [
70     'bluetooth_test_common',
71     'bluetoothtbd_test',
72     'net_test_avrcp',
73     'net_test_btcore',
74     'net_test_types',
75     'net_test_btm_iso',
76     'net_test_btpackets',
77 ]
78
79
80 class UseFlags():
81
82     def __init__(self, use_flags):
83         """ Construct the use flags.
84
85         Args:
86             use_flags: List of use flags parsed from the command.
87         """
88         self.flags = {}
89
90         # Import use flags required by common-mk
91         for use in COMMON_MK_USES:
92             self.set_flag(use, False)
93
94         # Set our defaults
95         for use, value in USE_DEFAULTS.items():
96             self.set_flag(use, value)
97
98         # Set use flags - value is set to True unless the use starts with -
99         # All given use flags always override the defaults
100         for use in use_flags:
101             value = not use.startswith('-')
102             self.set_flag(use, value)
103
104     def set_flag(self, key, value=True):
105         setattr(self, key, value)
106         self.flags[key] = value
107
108
109 class HostBuild():
110
111     def __init__(self, args):
112         """ Construct the builder.
113
114         Args:
115             args: Parsed arguments from ArgumentParser
116         """
117         self.args = args
118
119         # Set jobs to number of cpus unless explicitly set
120         self.jobs = self.args.jobs
121         if not self.jobs:
122             self.jobs = multiprocessing.cpu_count()
123             print("Number of jobs = {}".format(self.jobs))
124
125         # Normalize all directories
126         self.output_dir = os.path.abspath(self.args.output)
127         self.platform_dir = os.path.abspath(self.args.platform_dir)
128         self.sysroot = self.args.sysroot
129         self.use_board = os.path.abspath(self.args.use_board) if self.args.use_board else None
130         self.libdir = self.args.libdir
131
132         # If default target isn't set, build everything
133         self.target = 'all'
134         if hasattr(self.args, 'target') and self.args.target:
135             self.target = self.args.target
136
137         target_use = self.args.use if self.args.use else []
138
139         # Unless set, always build test code
140         if not self.args.notest:
141             target_use.append('test')
142
143         self.use = UseFlags(target_use)
144
145         # Validate platform directory
146         assert os.path.isdir(self.platform_dir), 'Platform dir does not exist'
147         assert os.path.isfile(os.path.join(self.platform_dir, '.gn')), 'Platform dir does not have .gn at root'
148
149         # Make sure output directory exists (or create it)
150         os.makedirs(self.output_dir, exist_ok=True)
151
152         # Set some default attributes
153         self.libbase_ver = None
154
155         self.configure_environ()
156
157     def _generate_rustflags(self):
158         """ Rustflags to include for the build.
159       """
160         rust_flags = [
161             '-L',
162             '{}/out/Default/'.format(self.output_dir),
163             '-C',
164             'link-arg=-Wl,--allow-multiple-definition',
165         ]
166
167         return ' '.join(rust_flags)
168
169     def configure_environ(self):
170         """ Configure environment variables for GN and Cargo.
171         """
172         self.env = os.environ.copy()
173
174         # Make sure cargo home dir exists and has a bin directory
175         cargo_home = os.path.join(self.output_dir, 'cargo_home')
176         os.makedirs(cargo_home, exist_ok=True)
177         os.makedirs(os.path.join(cargo_home, 'bin'), exist_ok=True)
178
179         # Configure Rust env variables
180         self.env['CARGO_TARGET_DIR'] = self.output_dir
181         self.env['CARGO_HOME'] = os.path.join(self.output_dir, 'cargo_home')
182         self.env['RUSTFLAGS'] = self._generate_rustflags()
183
184         # Configure some GN variables
185         if self.use_board:
186             self.env['PKG_CONFIG_PATH'] = os.path.join(self.use_board, self.libdir, 'pkgconfig')
187             libdir = os.path.join(self.use_board, self.libdir)
188             if self.env.get('LIBRARY_PATH'):
189                 libpath = self.env['LIBRARY_PATH']
190                 self.env['LIBRARY_PATH'] = '{}:{}'.format(libdir, libpath)
191             else:
192                 self.env['LIBRARY_PATH'] = libdir
193
194     def run_command(self, target, args, cwd=None, env=None):
195         """ Run command and stream the output.
196         """
197         # Set some defaults
198         if not cwd:
199             cwd = self.platform_dir
200         if not env:
201             env = self.env
202
203         log_file = os.path.join(self.output_dir, '{}.log'.format(target))
204         with open(log_file, 'wb') as lf:
205             rc = 0
206             process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
207             while True:
208                 line = process.stdout.readline()
209                 print(line.decode('utf-8'), end="")
210                 lf.write(line)
211                 if not line:
212                     rc = process.poll()
213                     if rc is not None:
214                         break
215
216                     time.sleep(0.1)
217
218             if rc != 0:
219                 raise Exception("Return code is {}".format(rc))
220
221     def _get_basever(self):
222         if self.libbase_ver:
223             return self.libbase_ver
224
225         self.libbase_ver = os.environ.get('BASE_VER', '')
226         if not self.libbase_ver:
227             base_file = os.path.join(self.sysroot, 'usr/share/libchrome/BASE_VER')
228             try:
229                 with open(base_file, 'r') as f:
230                     self.libbase_ver = f.read().strip('\n')
231             except:
232                 self.libbase_ver = 'NOT-INSTALLED'
233
234         return self.libbase_ver
235
236     def _gn_default_output(self):
237         return os.path.join(self.output_dir, 'out/Default')
238
239     def _gn_configure(self):
240         """ Configure all required parameters for platform2.
241
242         Mostly copied from //common-mk/platform2.py
243         """
244         clang = self.args.clang
245
246         def to_gn_string(s):
247             return '"%s"' % s.replace('"', '\\"')
248
249         def to_gn_list(strs):
250             return '[%s]' % ','.join([to_gn_string(s) for s in strs])
251
252         def to_gn_args_args(gn_args):
253             for k, v in gn_args.items():
254                 if isinstance(v, bool):
255                     v = str(v).lower()
256                 elif isinstance(v, list):
257                     v = to_gn_list(v)
258                 elif isinstance(v, six.string_types):
259                     v = to_gn_string(v)
260                 else:
261                     raise AssertionError('Unexpected %s, %r=%r' % (type(v), k, v))
262                 yield '%s=%s' % (k.replace('-', '_'), v)
263
264         gn_args = {
265             'platform_subdir': 'bt',
266             'cc': 'clang' if clang else 'gcc',
267             'cxx': 'clang++' if clang else 'g++',
268             'ar': 'llvm-ar' if clang else 'ar',
269             'pkg-config': 'pkg-config',
270             'clang_cc': clang,
271             'clang_cxx': clang,
272             'OS': 'linux',
273             'sysroot': self.sysroot,
274             'libdir': os.path.join(self.sysroot, self.libdir),
275             'build_root': self.output_dir,
276             'platform2_root': self.platform_dir,
277             'libbase_ver': self._get_basever(),
278             'enable_exceptions': os.environ.get('CXXEXCEPTIONS', 0) == '1',
279             'external_cflags': [],
280             'external_cxxflags': [],
281             'enable_werror': False,
282         }
283
284         if clang:
285             # Make sure to mark the clang use flag as true
286             self.use.set_flag('clang', True)
287             gn_args['external_cxxflags'] += ['-I/usr/include/']
288
289         # EXTREME HACK ALERT
290         #
291         # In my laziness, I am supporting building against an already built
292         # sysroot path (i.e. chromeos board) so that I don't have to build
293         # libchrome or modp_b64 locally.
294         if self.use_board:
295             includedir = os.path.join(self.use_board, 'usr/include')
296             gn_args['external_cxxflags'] += [
297                 '-I{}'.format(includedir),
298                 '-I{}/libchrome'.format(includedir),
299                 '-I{}/gtest'.format(includedir),
300                 '-I{}/gmock'.format(includedir),
301                 '-I{}/modp_b64'.format(includedir),
302             ]
303         gn_args_args = list(to_gn_args_args(gn_args))
304         use_args = ['%s=%s' % (k, str(v).lower()) for k, v in self.use.flags.items()]
305         gn_args_args += ['use={%s}' % (' '.join(use_args))]
306
307         gn_args = [
308             'gn',
309             'gen',
310         ]
311
312         if self.args.verbose:
313             gn_args.append('-v')
314
315         gn_args += [
316             '--root=%s' % self.platform_dir,
317             '--args=%s' % ' '.join(gn_args_args),
318             self._gn_default_output(),
319         ]
320
321         if 'PKG_CONFIG_PATH' in self.env:
322             print('DEBUG: PKG_CONFIG_PATH is', self.env['PKG_CONFIG_PATH'])
323
324         self.run_command('configure', gn_args)
325
326     def _gn_build(self, target):
327         """ Generate the ninja command for the target and run it.
328         """
329         args = ['%s:%s' % ('bt', target)]
330         ninja_args = ['ninja', '-C', self._gn_default_output()]
331         if self.jobs:
332             ninja_args += ['-j', str(self.jobs)]
333         ninja_args += args
334
335         if self.args.verbose:
336             ninja_args.append('-v')
337
338         self.run_command('build', ninja_args)
339
340     def _rust_configure(self):
341         """ Generate config file at cargo_home so we use vendored crates.
342         """
343         template = """
344         [source.systembt]
345         directory = "{}/external/rust/vendor"
346
347         [source.crates-io]
348         replace-with = "systembt"
349         local-registry = "/nonexistent"
350         """
351
352         if self.args.vendored_rust:
353             contents = template.format(self.platform_dir)
354             with open(os.path.join(self.env['CARGO_HOME'], 'config'), 'w') as f:
355                 f.write(contents)
356
357     def _rust_build(self):
358         """ Run `cargo build` from platform2/bt directory.
359         """
360         self.run_command('rust', ['cargo', 'build'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
361
362     def _target_prepare(self):
363         """ Target to prepare the output directory for building.
364
365         This runs gn gen to generate all rquired files and set up the Rust
366         config properly. This will be run
367         """
368         self._gn_configure()
369         self._rust_configure()
370
371     def _target_tools(self):
372         """ Build the tools target in an already prepared environment.
373         """
374         self._gn_build('tools')
375
376         # Also copy bluetooth_packetgen to CARGO_HOME so it's available
377         shutil.copy(
378             os.path.join(self._gn_default_output(), 'bluetooth_packetgen'), os.path.join(self.env['CARGO_HOME'], 'bin'))
379
380     def _target_rust(self):
381         """ Build rust artifacts in an already prepared environment.
382         """
383         self._rust_build()
384         rust_dir = os.path.join(self._gn_default_output(), 'rust')
385         if os.path.exists(rust_dir):
386             shutil.rmtree(rust_dir)
387         shutil.copytree(os.path.join(self.output_dir, 'debug'), rust_dir)
388
389     def _target_main(self):
390         """ Build the main GN artifacts in an already prepared environment.
391         """
392         self._gn_build('all')
393
394     def _target_test(self):
395         """ Runs the host tests.
396         """
397         # Rust tests first
398         self.run_command('test', ['cargo', 'test'], cwd=os.path.join(self.platform_dir, 'bt'), env=self.env)
399
400         # Host tests second based on host test list
401         for t in HOST_TESTS:
402             self.run_command(
403                 'test', [os.path.join(self.output_dir, 'out/Default', t)],
404                 cwd=os.path.join(self.output_dir),
405                 env=self.env)
406
407     def _target_clean(self):
408         """ Delete the output directory entirely.
409         """
410         shutil.rmtree(self.output_dir)
411
412     def _target_all(self):
413         """ Build all common targets (skipping test and clean).
414         """
415         self._target_prepare()
416         self._target_tools()
417         self._target_main()
418         self._target_rust()
419
420     def build(self):
421         """ Builds according to self.target
422         """
423         print('Building target ', self.target)
424
425         if self.target == 'prepare':
426             self._target_prepare()
427         elif self.target == 'tools':
428             self._target_tools()
429         elif self.target == 'rust':
430             self._target_rust()
431         elif self.target == 'main':
432             self._target_main()
433         elif self.target == 'test':
434             self._target_test()
435         elif self.target == 'clean':
436             self._target_clean()
437         elif self.target == 'all':
438             self._target_all()
439
440
441 if __name__ == '__main__':
442     parser = argparse.ArgumentParser(description='Simple build for host.')
443     parser.add_argument('--output', help='Output directory for the build.', required=True)
444     parser.add_argument('--platform-dir', help='Directory where platform2 is staged.', required=True)
445     parser.add_argument('--clang', help='Use clang compiler.', default=False, action='store_true')
446     parser.add_argument('--use', help='Set a specific use flag.')
447     parser.add_argument('--notest', help="Don't compile test code.", default=False, action='store_true')
448     parser.add_argument('--target', help='Run specific build target')
449     parser.add_argument('--sysroot', help='Set a specific sysroot path', default='/')
450     parser.add_argument('--libdir', help='Libdir - default = usr/lib64', default='usr/lib64')
451     parser.add_argument('--use-board', help='Use a built x86 board for dependencies. Provide path.')
452     parser.add_argument('--jobs', help='Number of jobs to run', default=0, type=int)
453     parser.add_argument('--vendored-rust', help='Use vendored rust crates', default=False, action='store_true')
454     parser.add_argument('--verbose', help='Verbose logs for build.')
455
456     args = parser.parse_args()
457     build = HostBuild(args)
458     build.build()