# Copyright (c) 2021 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from enum import Enum, auto from platform import uname from .gn import GnBuilder class HostCryptoLibrary(Enum): """Defines what cryptographic backend applications should use.""" OPENSSL = auto() MBEDTLS = auto() BORINGSSL = auto() @property def gn_argument(self): if self == HostCryptoLibrary.OPENSSL: return 'chip_crypto="openssl"' elif self == HostCryptoLibrary.MBEDTLS: return 'chip_crypto="mbedtls"' elif self == HostCryptoLibrary.BORINGSSL: return 'chip_crypto="boringssl"' class HostFuzzingType(Enum): """Defines fuzz target options available for host targets.""" NONE = auto() LIB_FUZZER = auto() OSS_FUZZ = auto() class HostApp(Enum): ALL_CLUSTERS = auto() ALL_CLUSTERS_MINIMAL = auto() CHIP_TOOL = auto() CHIP_TOOL_DARWIN = auto() THERMOSTAT = auto() RPC_CONSOLE = auto() MIN_MDNS = auto() ADDRESS_RESOLVE = auto() TV_APP = auto() TV_CASTING_APP = auto() LIGHT = auto() LOCK = auto() TESTS = auto() SHELL = auto() CERT_TOOL = auto() OTA_PROVIDER = auto() OTA_REQUESTOR = auto() SIMULATED_APP1 = auto() SIMULATED_APP2 = auto() PYTHON_BINDINGS = auto() EFR32_TEST_RUNNER = auto() TV_CASTING = auto() BRIDGE = auto() JAVA_MATTER_CONTROLLER = auto() CONTACT_SENSOR = auto() DISHWASHER = auto() REFRIGERATOR = auto() RVC = auto() def ExamplePath(self): if self == HostApp.ALL_CLUSTERS: return 'all-clusters-app/linux' elif self == HostApp.ALL_CLUSTERS_MINIMAL: return 'all-clusters-minimal-app/linux' elif self == HostApp.CHIP_TOOL: return 'chip-tool' elif self == HostApp.CHIP_TOOL_DARWIN: return 'darwin-framework-tool' elif self == HostApp.THERMOSTAT: return 'thermostat/linux' elif self == HostApp.RPC_CONSOLE: return 'common/pigweed/rpc_console' elif self == HostApp.MIN_MDNS: return 'minimal-mdns' elif self == HostApp.TV_APP: return 'tv-app/linux' elif self == HostApp.TV_CASTING_APP: return 'tv-casting-app/linux' elif self == HostApp.LIGHT: return 'lighting-app/linux' elif self == HostApp.LOCK: return 'lock-app/linux' elif self == HostApp.SHELL: return 'shell/standalone' elif self == HostApp.OTA_PROVIDER: return 'ota-provider-app/linux' elif self in [HostApp.SIMULATED_APP1, HostApp.SIMULATED_APP2]: return 'placeholder/linux/' elif self == HostApp.OTA_REQUESTOR: return 'ota-requestor-app/linux' elif self in [HostApp.ADDRESS_RESOLVE, HostApp.TESTS, HostApp.PYTHON_BINDINGS, HostApp.CERT_TOOL]: return '../' elif self == HostApp.EFR32_TEST_RUNNER: return '../src/test_driver/efr32' elif self == HostApp.TV_CASTING: return 'tv-casting-app/linux' elif self == HostApp.BRIDGE: return 'bridge-app/linux' elif self == HostApp.JAVA_MATTER_CONTROLLER: return 'java-matter-controller' elif self == HostApp.CONTACT_SENSOR: return 'contact-sensor-app/linux' elif self == HostApp.DISHWASHER: return 'dishwasher-app/linux' elif self == HostApp.REFRIGERATOR: return 'refrigerator-app/linux' elif self == HostApp.RVC: return 'rvc-app/linux' else: raise Exception('Unknown app type: %r' % self) def OutputNames(self): if self == HostApp.ALL_CLUSTERS: yield 'chip-all-clusters-app' yield 'chip-all-clusters-app.map' elif self == HostApp.ALL_CLUSTERS_MINIMAL: yield 'chip-all-clusters-minimal-app' yield 'chip-all-clusters-minimal-app.map' elif self == HostApp.CHIP_TOOL: yield 'chip-tool' yield 'chip-tool.map' elif self == HostApp.CHIP_TOOL_DARWIN: yield 'darwin-framework-tool' yield 'darwin-framework-tool.map' elif self == HostApp.THERMOSTAT: yield 'thermostat-app' yield 'thermostat-app.map' elif self == HostApp.RPC_CONSOLE: yield 'chip_rpc_console_wheels' elif self == HostApp.MIN_MDNS: yield 'mdns-advertiser' yield 'mdns-advertiser.map' yield 'minimal-mdns-client' yield 'minimal-mdns-client.map' yield 'minimal-mdns-server' yield 'minimal-mdns-server.map' elif self == HostApp.ADDRESS_RESOLVE: yield 'address-resolve-tool' yield 'address-resolve-tool.map' elif self == HostApp.TV_APP: yield 'chip-tv-app' yield 'chip-tv-app.map' elif self == HostApp.TV_CASTING_APP: yield 'chip-tv-casting-app' yield 'chip-tv-casting-app.map' elif self == HostApp.LIGHT: yield 'chip-lighting-app' yield 'chip-lighting-app.map' elif self == HostApp.LOCK: yield 'chip-lock-app' yield 'chip-lock-app.map' elif self == HostApp.TESTS: pass elif self == HostApp.SHELL: yield 'chip-shell' yield 'chip-shell.map' elif self == HostApp.CERT_TOOL: yield 'chip-cert' yield 'chip-cert.map' elif self == HostApp.SIMULATED_APP1: yield 'chip-app1' yield 'chip-app1.map' elif self == HostApp.SIMULATED_APP2: yield 'chip-app2' yield 'chip-app2.map' elif self == HostApp.OTA_PROVIDER: yield 'chip-ota-provider-app' yield 'chip-ota-provider-app.map' elif self == HostApp.OTA_REQUESTOR: yield 'chip-ota-requestor-app' yield 'chip-ota-requestor-app.map' elif self == HostApp.PYTHON_BINDINGS: yield 'controller/python' # Directory containing WHL files elif self == HostApp.EFR32_TEST_RUNNER: yield 'chip_nl_test_runner_wheels' elif self == HostApp.TV_CASTING: yield 'chip-tv-casting-app' yield 'chip-tv-casting-app.map' elif self == HostApp.BRIDGE: yield 'chip-bridge-app' yield 'chip-bridge-app.map' elif self == HostApp.JAVA_MATTER_CONTROLLER: yield 'java-matter-controller' yield 'java-matter-controller.map' elif self == HostApp.CONTACT_SENSOR: yield 'contact-sensor-app' yield 'contact-sensor-app.map' elif self == HostApp.DISHWASHER: yield 'dishwasher-app' yield 'dishwasher-app.map' elif self == HostApp.REFRIGERATOR: yield 'refrigerator-app' yield 'refrigerator-app.map' elif self == HostApp.RVC: yield 'rvc-app' yield 'rvc-app.map' else: raise Exception('Unknown app type: %r' % self) class HostBoard(Enum): NATIVE = auto() # cross-compile support ARM64 = auto() # for test support FAKE = auto() def BoardName(self): if self == HostBoard.NATIVE: uname_result = uname() arch = uname_result.machine # standardize some common platforms if arch == 'x86_64': arch = 'x64' elif arch == 'i386' or arch == 'i686': arch = 'x86' elif arch in ('aarch64', 'aarch64_be', 'armv8b', 'armv8l'): arch = 'arm64' return arch elif self == HostBoard.ARM64: return 'arm64' elif self == HostBoard.FAKE: return 'fake' else: raise Exception('Unknown host board type: %r' % self) def PlatformName(self): if self == HostBoard.NATIVE: return uname().system.lower() elif self == HostBoard.FAKE: return 'fake' else: # Cross compilation assumes linux currently return 'linux' class HostBuilder(GnBuilder): def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ipv4=True, enable_ble=True, enable_wifi=True, enable_thread=True, use_tsan=False, use_asan=False, use_ubsan=False, separate_event_loop=True, fuzzing_type: HostFuzzingType = HostFuzzingType.NONE, use_clang=False, interactive_mode=True, extra_tests=False, use_platform_mdns=False, enable_rpcs=False, use_coverage=False, use_dmalloc=False, minmdns_address_policy=None, minmdns_high_verbosity=False, imgui_ui=False, crypto_library: HostCryptoLibrary = None): super(HostBuilder, self).__init__( root=os.path.join(root, 'examples', app.ExamplePath()), runner=runner) self.app = app self.board = board self.extra_gn_options = [] self.build_env = {} if enable_rpcs: self.extra_gn_options.append('import("//with_pw_rpc.gni")') if not enable_ipv4: self.extra_gn_options.append('chip_inet_config_enable_ipv4=false') if not enable_ble: self.extra_gn_options.append('chip_config_network_layer_ble=false') if not enable_wifi: self.extra_gn_options.append('chip_enable_wifi=false') if not enable_thread: self.extra_gn_options.append('chip_enable_openthread=false') if use_tsan: self.extra_gn_options.append('is_tsan=true') if use_asan: self.extra_gn_options.append('is_asan=true') if use_ubsan: self.extra_gn_options.append('is_ubsan=true') if use_dmalloc: self.extra_gn_options.append('chip_config_memory_debug_checks=true') self.extra_gn_options.append('chip_config_memory_debug_dmalloc=true') # this is from `dmalloc -b -l DMALLOC_LOG -i 1 high` self.build_env['DMALLOC_OPTIONS'] = 'debug=0x4f4ed03,inter=1,log=DMALLOC_LOG' # glib interop with dmalloc self.build_env['G_SLICE'] = 'always-malloc' if not separate_event_loop: self.extra_gn_options.append('config_use_separate_eventloop=false') if not interactive_mode: self.extra_gn_options.append('config_use_interactive_mode=false') if fuzzing_type == HostFuzzingType.LIB_FUZZER: self.extra_gn_options.append('is_libfuzzer=true') elif fuzzing_type == HostFuzzingType.OSS_FUZZ: self.extra_gn_options.append('oss_fuzz=true') if imgui_ui: self.extra_gn_options.append('chip_examples_enable_imgui_ui=true') self.use_coverage = use_coverage if use_coverage: self.extra_gn_options.append('use_coverage=true') if use_clang: self.extra_gn_options.append('is_clang=true') if self.board == HostBoard.FAKE: # Fake uses "//build/toolchain/fake:fake_x64_gcc" # so setting clang is not correct raise Exception('Fake host board is always gcc (not clang)') if minmdns_address_policy: if use_platform_mdns: raise Exception('Address policy applies to minmdns only') self.extra_gn_options.append('chip_minmdns_default_policy="%s"' % minmdns_address_policy) if use_platform_mdns: self.extra_gn_options.append('chip_mdns="platform"') if extra_tests: # Flag for testing purpose self.extra_gn_options.append( 'chip_im_force_fabric_quota_check=true') if minmdns_high_verbosity: self.extra_gn_options.append('chip_minmdns_high_verbosity=true') if app == HostApp.TESTS: self.extra_gn_options.append('chip_build_tests=true') self.build_command = 'check' if app == HostApp.EFR32_TEST_RUNNER: self.build_command = 'runner' # board will NOT be used, but is required to be able to properly # include things added by the test_runner efr32 build self.extra_gn_options.append('silabs_board="BRD4161A"') # Crypto library has per-platform defaults (like openssl for linux/mac # and mbedtls for android/freertos/zephyr/mbed/...) if crypto_library: self.extra_gn_options.append(crypto_library.gn_argument) if self.board == HostBoard.ARM64: if not use_clang: raise Exception("Cross compile only supported using clang") if app == HostApp.CERT_TOOL: # Certification only built for openssl if self.board == HostBoard.ARM64 and crypto_library == HostCryptoLibrary.MBEDTLS: raise Exception("MbedTLS not supported for cross compiling cert tool") self.build_command = 'src/tools/chip-cert' elif app == HostApp.ADDRESS_RESOLVE: self.build_command = 'src/lib/address_resolve:address-resolve-tool' elif app == HostApp.PYTHON_BINDINGS: self.extra_gn_options.append('enable_rtti=false') self.extra_gn_options.append('chip_project_config_include_dirs=["//config/python"]') self.build_command = 'chip-repl' if self.app == HostApp.SIMULATED_APP1: self.extra_gn_options.append('chip_tests_zap_config="app1"') if self.app == HostApp.SIMULATED_APP2: self.extra_gn_options.append('chip_tests_zap_config="app2"') if self.app == HostApp.TESTS and fuzzing_type != HostFuzzingType.NONE: self.build_command = 'fuzz_tests' def GnBuildArgs(self): if self.board == HostBoard.NATIVE: return self.extra_gn_options elif self.board == HostBoard.ARM64: self.extra_gn_options.extend( [ 'target_cpu="arm64"', 'sysroot="%s"' % self.SysRootPath('SYSROOT_AARCH64') ] ) return self.extra_gn_options elif self.board == HostBoard.FAKE: self.extra_gn_options.extend( [ 'custom_toolchain="//build/toolchain/fake:fake_x64_gcc"', 'chip_link_tests=true', 'chip_device_platform="fake"', 'chip_fake_platform=true', ] ) return self.extra_gn_options else: raise Exception('Unknown host board type: %r' % self) def createJavaExecutable(self, java_program): self._Execute( [ "chmod", "+x", "%s/bin/%s" % (self.output_dir, java_program), ], title="Make Java program executable", ) def GnBuildEnv(self): if self.board == HostBoard.ARM64: self.build_env['PKG_CONFIG_PATH'] = os.path.join( self.SysRootPath('SYSROOT_AARCH64'), 'lib/aarch64-linux-gnu/pkgconfig') return self.build_env def SysRootPath(self, name): if name not in os.environ: raise Exception('Missing environment variable "%s"' % name) return os.environ[name] def generate(self): super(HostBuilder, self).generate() if 'JAVA_PATH' in os.environ: self._Execute( ["third_party/java_deps/set_up_java_deps.sh"], title="Setting up Java deps", ) exampleName = self.app.ExamplePath() if exampleName == "java-matter-controller": self._Execute( [ "cp", os.path.join(self.root, "Manifest.txt"), self.output_dir, ], title="Copying Manifest.txt to " + self.output_dir, ) if self.app == HostApp.TESTS and self.use_coverage: self.coverage_dir = os.path.join(self.output_dir, 'coverage') self._Execute(['mkdir', '-p', self.coverage_dir], title="Create coverage output location") def PreBuildCommand(self): if self.app == HostApp.TESTS and self.use_coverage: self._Execute(['ninja', '-C', self.output_dir, 'default'], title="Build-only") self._Execute(['find', os.path.join(self.output_dir, 'obj/src/'), '-depth', '-name', 'tests', '-exec', 'rm -rf {} \\;'], title="Cleanup unit tests") self._Execute(['lcov', '--initial', '--capture', '--directory', os.path.join(self.output_dir, 'obj'), '--exclude', os.path.join(self.chip_dir, 'zzz_generated/*'), '--exclude', os.path.join(self.chip_dir, 'third_party/*'), '--exclude', '/usr/include/*', '--output-file', os.path.join(self.coverage_dir, 'lcov_base.info')], title="Initial coverage baseline") def PostBuildCommand(self): if self.app == HostApp.TESTS and self.use_coverage: self._Execute(['lcov', '--capture', '--directory', os.path.join(self.output_dir, 'obj'), '--exclude', os.path.join(self.chip_dir, 'zzz_generated/*'), '--exclude', os.path.join(self.chip_dir, 'third_party/*'), '--exclude', '/usr/include/*', '--output-file', os.path.join(self.coverage_dir, 'lcov_test.info')], title="Update coverage") self._Execute(['lcov', '--add-tracefile', os.path.join(self.coverage_dir, 'lcov_base.info'), '--add-tracefile', os.path.join(self.coverage_dir, 'lcov_test.info'), '--output-file', os.path.join(self.coverage_dir, 'lcov_final.info') ], title="Final coverage info") self._Execute(['genhtml', os.path.join(self.coverage_dir, 'lcov_final.info'), '--output-directory', os.path.join(self.coverage_dir, 'html')], title="HTML coverage") if self.app == HostApp.JAVA_MATTER_CONTROLLER: self.createJavaExecutable("java-matter-controller") def build_outputs(self): outputs = {} for name in self.app.OutputNames(): path = os.path.join(self.output_dir, name) if os.path.isdir(path): for root, dirs, files in os.walk(path): for file in files: outputs.update({ file: os.path.join(root, file) }) else: outputs.update({ name: os.path.join(self.output_dir, name) }) return outputs