host.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. # Copyright (c) 2021 Project CHIP Authors
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os
  15. from enum import Enum, auto
  16. from platform import uname
  17. from .gn import GnBuilder
  18. class HostCryptoLibrary(Enum):
  19. """Defines what cryptographic backend applications should use."""
  20. OPENSSL = auto()
  21. MBEDTLS = auto()
  22. BORINGSSL = auto()
  23. @property
  24. def gn_argument(self):
  25. if self == HostCryptoLibrary.OPENSSL:
  26. return 'chip_crypto="openssl"'
  27. elif self == HostCryptoLibrary.MBEDTLS:
  28. return 'chip_crypto="mbedtls"'
  29. elif self == HostCryptoLibrary.BORINGSSL:
  30. return 'chip_crypto="boringssl"'
  31. class HostFuzzingType(Enum):
  32. """Defines fuzz target options available for host targets."""
  33. NONE = auto()
  34. LIB_FUZZER = auto()
  35. OSS_FUZZ = auto()
  36. class HostApp(Enum):
  37. ALL_CLUSTERS = auto()
  38. ALL_CLUSTERS_MINIMAL = auto()
  39. CHIP_TOOL = auto()
  40. CHIP_TOOL_DARWIN = auto()
  41. THERMOSTAT = auto()
  42. RPC_CONSOLE = auto()
  43. MIN_MDNS = auto()
  44. ADDRESS_RESOLVE = auto()
  45. TV_APP = auto()
  46. TV_CASTING_APP = auto()
  47. LIGHT = auto()
  48. LOCK = auto()
  49. TESTS = auto()
  50. SHELL = auto()
  51. CERT_TOOL = auto()
  52. OTA_PROVIDER = auto()
  53. OTA_REQUESTOR = auto()
  54. SIMULATED_APP1 = auto()
  55. SIMULATED_APP2 = auto()
  56. PYTHON_BINDINGS = auto()
  57. EFR32_TEST_RUNNER = auto()
  58. TV_CASTING = auto()
  59. BRIDGE = auto()
  60. JAVA_MATTER_CONTROLLER = auto()
  61. CONTACT_SENSOR = auto()
  62. DISHWASHER = auto()
  63. REFRIGERATOR = auto()
  64. RVC = auto()
  65. def ExamplePath(self):
  66. if self == HostApp.ALL_CLUSTERS:
  67. return 'all-clusters-app/linux'
  68. elif self == HostApp.ALL_CLUSTERS_MINIMAL:
  69. return 'all-clusters-minimal-app/linux'
  70. elif self == HostApp.CHIP_TOOL:
  71. return 'chip-tool'
  72. elif self == HostApp.CHIP_TOOL_DARWIN:
  73. return 'darwin-framework-tool'
  74. elif self == HostApp.THERMOSTAT:
  75. return 'thermostat/linux'
  76. elif self == HostApp.RPC_CONSOLE:
  77. return 'common/pigweed/rpc_console'
  78. elif self == HostApp.MIN_MDNS:
  79. return 'minimal-mdns'
  80. elif self == HostApp.TV_APP:
  81. return 'tv-app/linux'
  82. elif self == HostApp.TV_CASTING_APP:
  83. return 'tv-casting-app/linux'
  84. elif self == HostApp.LIGHT:
  85. return 'lighting-app/linux'
  86. elif self == HostApp.LOCK:
  87. return 'lock-app/linux'
  88. elif self == HostApp.SHELL:
  89. return 'shell/standalone'
  90. elif self == HostApp.OTA_PROVIDER:
  91. return 'ota-provider-app/linux'
  92. elif self in [HostApp.SIMULATED_APP1, HostApp.SIMULATED_APP2]:
  93. return 'placeholder/linux/'
  94. elif self == HostApp.OTA_REQUESTOR:
  95. return 'ota-requestor-app/linux'
  96. elif self in [HostApp.ADDRESS_RESOLVE, HostApp.TESTS, HostApp.PYTHON_BINDINGS, HostApp.CERT_TOOL]:
  97. return '../'
  98. elif self == HostApp.EFR32_TEST_RUNNER:
  99. return '../src/test_driver/efr32'
  100. elif self == HostApp.TV_CASTING:
  101. return 'tv-casting-app/linux'
  102. elif self == HostApp.BRIDGE:
  103. return 'bridge-app/linux'
  104. elif self == HostApp.JAVA_MATTER_CONTROLLER:
  105. return 'java-matter-controller'
  106. elif self == HostApp.CONTACT_SENSOR:
  107. return 'contact-sensor-app/linux'
  108. elif self == HostApp.DISHWASHER:
  109. return 'dishwasher-app/linux'
  110. elif self == HostApp.REFRIGERATOR:
  111. return 'refrigerator-app/linux'
  112. elif self == HostApp.RVC:
  113. return 'rvc-app/linux'
  114. else:
  115. raise Exception('Unknown app type: %r' % self)
  116. def OutputNames(self):
  117. if self == HostApp.ALL_CLUSTERS:
  118. yield 'chip-all-clusters-app'
  119. yield 'chip-all-clusters-app.map'
  120. elif self == HostApp.ALL_CLUSTERS_MINIMAL:
  121. yield 'chip-all-clusters-minimal-app'
  122. yield 'chip-all-clusters-minimal-app.map'
  123. elif self == HostApp.CHIP_TOOL:
  124. yield 'chip-tool'
  125. yield 'chip-tool.map'
  126. elif self == HostApp.CHIP_TOOL_DARWIN:
  127. yield 'darwin-framework-tool'
  128. yield 'darwin-framework-tool.map'
  129. elif self == HostApp.THERMOSTAT:
  130. yield 'thermostat-app'
  131. yield 'thermostat-app.map'
  132. elif self == HostApp.RPC_CONSOLE:
  133. yield 'chip_rpc_console_wheels'
  134. elif self == HostApp.MIN_MDNS:
  135. yield 'mdns-advertiser'
  136. yield 'mdns-advertiser.map'
  137. yield 'minimal-mdns-client'
  138. yield 'minimal-mdns-client.map'
  139. yield 'minimal-mdns-server'
  140. yield 'minimal-mdns-server.map'
  141. elif self == HostApp.ADDRESS_RESOLVE:
  142. yield 'address-resolve-tool'
  143. yield 'address-resolve-tool.map'
  144. elif self == HostApp.TV_APP:
  145. yield 'chip-tv-app'
  146. yield 'chip-tv-app.map'
  147. elif self == HostApp.TV_CASTING_APP:
  148. yield 'chip-tv-casting-app'
  149. yield 'chip-tv-casting-app.map'
  150. elif self == HostApp.LIGHT:
  151. yield 'chip-lighting-app'
  152. yield 'chip-lighting-app.map'
  153. elif self == HostApp.LOCK:
  154. yield 'chip-lock-app'
  155. yield 'chip-lock-app.map'
  156. elif self == HostApp.TESTS:
  157. pass
  158. elif self == HostApp.SHELL:
  159. yield 'chip-shell'
  160. yield 'chip-shell.map'
  161. elif self == HostApp.CERT_TOOL:
  162. yield 'chip-cert'
  163. yield 'chip-cert.map'
  164. elif self == HostApp.SIMULATED_APP1:
  165. yield 'chip-app1'
  166. yield 'chip-app1.map'
  167. elif self == HostApp.SIMULATED_APP2:
  168. yield 'chip-app2'
  169. yield 'chip-app2.map'
  170. elif self == HostApp.OTA_PROVIDER:
  171. yield 'chip-ota-provider-app'
  172. yield 'chip-ota-provider-app.map'
  173. elif self == HostApp.OTA_REQUESTOR:
  174. yield 'chip-ota-requestor-app'
  175. yield 'chip-ota-requestor-app.map'
  176. elif self == HostApp.PYTHON_BINDINGS:
  177. yield 'controller/python' # Directory containing WHL files
  178. elif self == HostApp.EFR32_TEST_RUNNER:
  179. yield 'chip_nl_test_runner_wheels'
  180. elif self == HostApp.TV_CASTING:
  181. yield 'chip-tv-casting-app'
  182. yield 'chip-tv-casting-app.map'
  183. elif self == HostApp.BRIDGE:
  184. yield 'chip-bridge-app'
  185. yield 'chip-bridge-app.map'
  186. elif self == HostApp.JAVA_MATTER_CONTROLLER:
  187. yield 'java-matter-controller'
  188. yield 'java-matter-controller.map'
  189. elif self == HostApp.CONTACT_SENSOR:
  190. yield 'contact-sensor-app'
  191. yield 'contact-sensor-app.map'
  192. elif self == HostApp.DISHWASHER:
  193. yield 'dishwasher-app'
  194. yield 'dishwasher-app.map'
  195. elif self == HostApp.REFRIGERATOR:
  196. yield 'refrigerator-app'
  197. yield 'refrigerator-app.map'
  198. elif self == HostApp.RVC:
  199. yield 'rvc-app'
  200. yield 'rvc-app.map'
  201. else:
  202. raise Exception('Unknown app type: %r' % self)
  203. class HostBoard(Enum):
  204. NATIVE = auto()
  205. # cross-compile support
  206. ARM64 = auto()
  207. # for test support
  208. FAKE = auto()
  209. def BoardName(self):
  210. if self == HostBoard.NATIVE:
  211. uname_result = uname()
  212. arch = uname_result.machine
  213. # standardize some common platforms
  214. if arch == 'x86_64':
  215. arch = 'x64'
  216. elif arch == 'i386' or arch == 'i686':
  217. arch = 'x86'
  218. elif arch in ('aarch64', 'aarch64_be', 'armv8b', 'armv8l'):
  219. arch = 'arm64'
  220. return arch
  221. elif self == HostBoard.ARM64:
  222. return 'arm64'
  223. elif self == HostBoard.FAKE:
  224. return 'fake'
  225. else:
  226. raise Exception('Unknown host board type: %r' % self)
  227. def PlatformName(self):
  228. if self == HostBoard.NATIVE:
  229. return uname().system.lower()
  230. elif self == HostBoard.FAKE:
  231. return 'fake'
  232. else:
  233. # Cross compilation assumes linux currently
  234. return 'linux'
  235. class HostBuilder(GnBuilder):
  236. def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE,
  237. enable_ipv4=True, enable_ble=True, enable_wifi=True,
  238. enable_thread=True, use_tsan=False, use_asan=False, use_ubsan=False,
  239. separate_event_loop=True, fuzzing_type: HostFuzzingType = HostFuzzingType.NONE, use_clang=False,
  240. interactive_mode=True, extra_tests=False, use_platform_mdns=False, enable_rpcs=False,
  241. use_coverage=False, use_dmalloc=False, minmdns_address_policy=None,
  242. minmdns_high_verbosity=False, imgui_ui=False, crypto_library: HostCryptoLibrary = None):
  243. super(HostBuilder, self).__init__(
  244. root=os.path.join(root, 'examples', app.ExamplePath()),
  245. runner=runner)
  246. self.app = app
  247. self.board = board
  248. self.extra_gn_options = []
  249. self.build_env = {}
  250. if enable_rpcs:
  251. self.extra_gn_options.append('import("//with_pw_rpc.gni")')
  252. if not enable_ipv4:
  253. self.extra_gn_options.append('chip_inet_config_enable_ipv4=false')
  254. if not enable_ble:
  255. self.extra_gn_options.append('chip_config_network_layer_ble=false')
  256. if not enable_wifi:
  257. self.extra_gn_options.append('chip_enable_wifi=false')
  258. if not enable_thread:
  259. self.extra_gn_options.append('chip_enable_openthread=false')
  260. if use_tsan:
  261. self.extra_gn_options.append('is_tsan=true')
  262. if use_asan:
  263. self.extra_gn_options.append('is_asan=true')
  264. if use_ubsan:
  265. self.extra_gn_options.append('is_ubsan=true')
  266. if use_dmalloc:
  267. self.extra_gn_options.append('chip_config_memory_debug_checks=true')
  268. self.extra_gn_options.append('chip_config_memory_debug_dmalloc=true')
  269. # this is from `dmalloc -b -l DMALLOC_LOG -i 1 high`
  270. self.build_env['DMALLOC_OPTIONS'] = 'debug=0x4f4ed03,inter=1,log=DMALLOC_LOG'
  271. # glib interop with dmalloc
  272. self.build_env['G_SLICE'] = 'always-malloc'
  273. if not separate_event_loop:
  274. self.extra_gn_options.append('config_use_separate_eventloop=false')
  275. if not interactive_mode:
  276. self.extra_gn_options.append('config_use_interactive_mode=false')
  277. if fuzzing_type == HostFuzzingType.LIB_FUZZER:
  278. self.extra_gn_options.append('is_libfuzzer=true')
  279. elif fuzzing_type == HostFuzzingType.OSS_FUZZ:
  280. self.extra_gn_options.append('oss_fuzz=true')
  281. if imgui_ui:
  282. self.extra_gn_options.append('chip_examples_enable_imgui_ui=true')
  283. self.use_coverage = use_coverage
  284. if use_coverage:
  285. self.extra_gn_options.append('use_coverage=true')
  286. if use_clang:
  287. self.extra_gn_options.append('is_clang=true')
  288. if self.board == HostBoard.FAKE:
  289. # Fake uses "//build/toolchain/fake:fake_x64_gcc"
  290. # so setting clang is not correct
  291. raise Exception('Fake host board is always gcc (not clang)')
  292. if minmdns_address_policy:
  293. if use_platform_mdns:
  294. raise Exception('Address policy applies to minmdns only')
  295. self.extra_gn_options.append('chip_minmdns_default_policy="%s"' % minmdns_address_policy)
  296. if use_platform_mdns:
  297. self.extra_gn_options.append('chip_mdns="platform"')
  298. if extra_tests:
  299. # Flag for testing purpose
  300. self.extra_gn_options.append(
  301. 'chip_im_force_fabric_quota_check=true')
  302. if minmdns_high_verbosity:
  303. self.extra_gn_options.append('chip_minmdns_high_verbosity=true')
  304. if app == HostApp.TESTS:
  305. self.extra_gn_options.append('chip_build_tests=true')
  306. self.build_command = 'check'
  307. if app == HostApp.EFR32_TEST_RUNNER:
  308. self.build_command = 'runner'
  309. # board will NOT be used, but is required to be able to properly
  310. # include things added by the test_runner efr32 build
  311. self.extra_gn_options.append('silabs_board="BRD4161A"')
  312. # Crypto library has per-platform defaults (like openssl for linux/mac
  313. # and mbedtls for android/freertos/zephyr/mbed/...)
  314. if crypto_library:
  315. self.extra_gn_options.append(crypto_library.gn_argument)
  316. if self.board == HostBoard.ARM64:
  317. if not use_clang:
  318. raise Exception("Cross compile only supported using clang")
  319. if app == HostApp.CERT_TOOL:
  320. # Certification only built for openssl
  321. if self.board == HostBoard.ARM64 and crypto_library == HostCryptoLibrary.MBEDTLS:
  322. raise Exception("MbedTLS not supported for cross compiling cert tool")
  323. self.build_command = 'src/tools/chip-cert'
  324. elif app == HostApp.ADDRESS_RESOLVE:
  325. self.build_command = 'src/lib/address_resolve:address-resolve-tool'
  326. elif app == HostApp.PYTHON_BINDINGS:
  327. self.extra_gn_options.append('enable_rtti=false')
  328. self.extra_gn_options.append('chip_project_config_include_dirs=["//config/python"]')
  329. self.build_command = 'chip-repl'
  330. if self.app == HostApp.SIMULATED_APP1:
  331. self.extra_gn_options.append('chip_tests_zap_config="app1"')
  332. if self.app == HostApp.SIMULATED_APP2:
  333. self.extra_gn_options.append('chip_tests_zap_config="app2"')
  334. if self.app == HostApp.TESTS and fuzzing_type != HostFuzzingType.NONE:
  335. self.build_command = 'fuzz_tests'
  336. def GnBuildArgs(self):
  337. if self.board == HostBoard.NATIVE:
  338. return self.extra_gn_options
  339. elif self.board == HostBoard.ARM64:
  340. self.extra_gn_options.extend(
  341. [
  342. 'target_cpu="arm64"',
  343. 'sysroot="%s"' % self.SysRootPath('SYSROOT_AARCH64')
  344. ]
  345. )
  346. return self.extra_gn_options
  347. elif self.board == HostBoard.FAKE:
  348. self.extra_gn_options.extend(
  349. [
  350. 'custom_toolchain="//build/toolchain/fake:fake_x64_gcc"',
  351. 'chip_link_tests=true',
  352. 'chip_device_platform="fake"',
  353. 'chip_fake_platform=true',
  354. ]
  355. )
  356. return self.extra_gn_options
  357. else:
  358. raise Exception('Unknown host board type: %r' % self)
  359. def createJavaExecutable(self, java_program):
  360. self._Execute(
  361. [
  362. "chmod",
  363. "+x",
  364. "%s/bin/%s" % (self.output_dir, java_program),
  365. ],
  366. title="Make Java program executable",
  367. )
  368. def GnBuildEnv(self):
  369. if self.board == HostBoard.ARM64:
  370. self.build_env['PKG_CONFIG_PATH'] = os.path.join(
  371. self.SysRootPath('SYSROOT_AARCH64'), 'lib/aarch64-linux-gnu/pkgconfig')
  372. return self.build_env
  373. def SysRootPath(self, name):
  374. if name not in os.environ:
  375. raise Exception('Missing environment variable "%s"' % name)
  376. return os.environ[name]
  377. def generate(self):
  378. super(HostBuilder, self).generate()
  379. if 'JAVA_PATH' in os.environ:
  380. self._Execute(
  381. ["third_party/java_deps/set_up_java_deps.sh"],
  382. title="Setting up Java deps",
  383. )
  384. exampleName = self.app.ExamplePath()
  385. if exampleName == "java-matter-controller":
  386. self._Execute(
  387. [
  388. "cp",
  389. os.path.join(self.root, "Manifest.txt"),
  390. self.output_dir,
  391. ],
  392. title="Copying Manifest.txt to " + self.output_dir,
  393. )
  394. if self.app == HostApp.TESTS and self.use_coverage:
  395. self.coverage_dir = os.path.join(self.output_dir, 'coverage')
  396. self._Execute(['mkdir', '-p', self.coverage_dir], title="Create coverage output location")
  397. def PreBuildCommand(self):
  398. if self.app == HostApp.TESTS and self.use_coverage:
  399. self._Execute(['ninja', '-C', self.output_dir, 'default'], title="Build-only")
  400. self._Execute(['find', os.path.join(self.output_dir, 'obj/src/'), '-depth',
  401. '-name', 'tests', '-exec', 'rm -rf {} \\;'], title="Cleanup unit tests")
  402. self._Execute(['lcov', '--initial', '--capture', '--directory', os.path.join(self.output_dir, 'obj'),
  403. '--exclude', os.path.join(self.chip_dir, 'zzz_generated/*'),
  404. '--exclude', os.path.join(self.chip_dir, 'third_party/*'),
  405. '--exclude', '/usr/include/*',
  406. '--output-file', os.path.join(self.coverage_dir, 'lcov_base.info')], title="Initial coverage baseline")
  407. def PostBuildCommand(self):
  408. if self.app == HostApp.TESTS and self.use_coverage:
  409. self._Execute(['lcov', '--capture', '--directory', os.path.join(self.output_dir, 'obj'),
  410. '--exclude', os.path.join(self.chip_dir, 'zzz_generated/*'),
  411. '--exclude', os.path.join(self.chip_dir, 'third_party/*'),
  412. '--exclude', '/usr/include/*',
  413. '--output-file', os.path.join(self.coverage_dir, 'lcov_test.info')], title="Update coverage")
  414. self._Execute(['lcov', '--add-tracefile', os.path.join(self.coverage_dir, 'lcov_base.info'),
  415. '--add-tracefile', os.path.join(self.coverage_dir, 'lcov_test.info'),
  416. '--output-file', os.path.join(self.coverage_dir, 'lcov_final.info')
  417. ], title="Final coverage info")
  418. self._Execute(['genhtml', os.path.join(self.coverage_dir, 'lcov_final.info'), '--output-directory',
  419. os.path.join(self.coverage_dir, 'html')], title="HTML coverage")
  420. if self.app == HostApp.JAVA_MATTER_CONTROLLER:
  421. self.createJavaExecutable("java-matter-controller")
  422. def build_outputs(self):
  423. outputs = {}
  424. for name in self.app.OutputNames():
  425. path = os.path.join(self.output_dir, name)
  426. if os.path.isdir(path):
  427. for root, dirs, files in os.walk(path):
  428. for file in files:
  429. outputs.update({
  430. file: os.path.join(root, file)
  431. })
  432. else:
  433. outputs.update({
  434. name: os.path.join(self.output_dir, name)
  435. })
  436. return outputs