| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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 |
| import platform |
| from pathlib import Path |
| |
| import inquirer |
| import yaml |
| from minifi_option import MinifiOptions |
| from package_manager import PackageManager |
| from system_dependency import install_required |
| |
| |
| def install_dependencies( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| res = install_required(minifi_options, package_manager) |
| print( |
| "Installation went smoothly" |
| if res |
| else "There were some error during installation" |
| ) |
| return res |
| |
| |
| def export_custom_conan_recipes( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| thirdparty_dir = minifi_options.source_dir / "thirdparty" |
| for root, _, files in os.walk(thirdparty_dir): |
| for file in files: |
| if file == "conanfile.py": |
| config_yaml = os.path.join(Path(root).parent, "config.yml") |
| |
| with open(config_yaml) as f: |
| data = yaml.safe_load(f) |
| |
| version = next(iter(data["versions"])) |
| |
| print( |
| f"Exporting the custom Conan recipe {root} with version {version}" |
| ) |
| if not package_manager.run_cmd( |
| f'conan export "{root}" --version={version} --user=minifi --channel=develop' |
| ): |
| print(f"Exporting the custom Conan recipe {root} failed") |
| return False |
| return True |
| |
| |
| def add_conan_options_from_cmake_options( |
| extension_options: list[str], minifi_options: MinifiOptions |
| ) -> str: |
| conan_options = "" |
| for extension_option in extension_options: |
| if minifi_options.bool_options[extension_option.upper()].value not in ( |
| None, |
| "OFF", |
| ): |
| conan_options += f' -o "&:{extension_option.lower()}=True"' |
| return conan_options |
| |
| |
| def run_conan_install( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| if minifi_options.use_conan.value != "ON": |
| print("Conan install skipped because USE_CONAN is OFF") |
| return True |
| conan_options = add_conan_options_from_cmake_options( |
| [ |
| "ENABLE_ALL", |
| "ENABLE_LIBARCHIVE", |
| "ENABLE_ROCKSDB", |
| "ENABLE_SFTP", |
| "ENABLE_PROMETHEUS", |
| "ENABLE_BZIP2", |
| "ENABLE_LZMA", |
| "ENABLE_MQTT", |
| "ENABLE_COUCHBASE", |
| "ENABLE_KAFKA", |
| "ENABLE_OPC", |
| "ENABLE_GCP", |
| "ENABLE_GRPC_FOR_LOKI", |
| "ENABLE_BUSTACHE", |
| "ENABLE_KUBERNETES", |
| "ENABLE_AZURE", |
| "ENABLE_LLAMACPP", |
| "ENABLE_AWS", |
| "PORTABLE", |
| "SKIP_TESTS", |
| ], |
| minifi_options, |
| ) |
| if ( |
| minifi_options.custom_malloc is not None |
| and minifi_options.custom_malloc.value not in (None, "OFF") |
| ): |
| conan_options += f' -o "&:custom_malloc={minifi_options.custom_malloc.value}"' |
| |
| if not package_manager.run_cmd("conan profile detect --exist-ok"): |
| print("Conan default profile detection failed") |
| return False |
| |
| if not export_custom_conan_recipes(minifi_options, package_manager): |
| return False |
| |
| compiler_settings = " -s:a compiler.cppstd=23" |
| generator_setting = ( |
| " -c tools.cmake.cmaketoolchain:generator=Ninja" |
| if minifi_options.use_ninja.value == "ON" |
| else "" |
| ) |
| conan_remote_add_cmd = "conan remote add nifi-conan https://apache.jfrog.io/artifactory/api/conan/nifi-conan --force" |
| if not package_manager.run_cmd(conan_remote_add_cmd): |
| print("Adding the nifi-conan remote failed") |
| return False |
| build_cmd = ( |
| f'conan install "{minifi_options.source_dir}" --output-folder="{minifi_options.build_dir}" --build=missing {conan_options} ' |
| f"--settings=build_type={minifi_options.build_type.value}{generator_setting}{compiler_settings}" |
| ) |
| res = package_manager.run_cmd(build_cmd) |
| print("Conan install was successful" if res else "Conan install was unsuccessful") |
| return res |
| |
| |
| def _conan_build_env_prefix(minifi_options: MinifiOptions) -> str: |
| if minifi_options.use_conan.value == "ON" and platform.system() == "Windows": |
| conanbuild = os.path.join(str(minifi_options.build_dir), "conanbuild.bat") |
| return f'call "{conanbuild}" && ' |
| return "" |
| |
| |
| def run_cmake(minifi_options: MinifiOptions, package_manager: PackageManager): |
| if not os.path.exists(minifi_options.build_dir): |
| os.mkdir(minifi_options.build_dir) |
| cmake_cmd = ( |
| f"{_conan_build_env_prefix(minifi_options)}cmake {minifi_options.create_cmake_generator_str()} {minifi_options.create_cmake_use_conan_str()} " |
| f'{minifi_options.create_cmake_options_str()} "{minifi_options.source_dir}" -B "{minifi_options.build_dir}"' |
| ) |
| res = package_manager.run_cmd(cmake_cmd) |
| print( |
| "CMake command run successfully" if res else "CMake command run unsuccessfully" |
| ) |
| return res |
| |
| |
| def do_build(minifi_options: MinifiOptions, package_manager: PackageManager): |
| build_cmd = f'{_conan_build_env_prefix(minifi_options)}cmake --build "{minifi_options.build_dir!s}" {minifi_options.create_cmake_build_flags_str()}' |
| res = package_manager.run_cmd(build_cmd) |
| print("Build was successful" if res else "Build was unsuccessful") |
| return res |
| |
| |
| def do_package(minifi_options: MinifiOptions, package_manager: PackageManager): |
| build_cmd = f'{_conan_build_env_prefix(minifi_options)}cmake --build "{minifi_options.build_dir!s}" --target package {minifi_options.create_cmake_build_flags_str()}' |
| return package_manager.run_cmd(build_cmd) |
| |
| |
| def do_docker_build(minifi_options: MinifiOptions, package_manager: PackageManager): |
| build_cmd = f'cmake --build "{minifi_options.build_dir!s}" --target docker' |
| return package_manager.run_cmd(build_cmd) |
| |
| |
| def do_one_click_build( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| assert install_dependencies(minifi_options, package_manager) |
| assert run_conan_install(minifi_options, package_manager) |
| assert run_cmake(minifi_options, package_manager) |
| assert do_build(minifi_options, package_manager) |
| assert do_package(minifi_options, package_manager) |
| return True |
| |
| |
| def do_one_click_configuration( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| assert install_dependencies(minifi_options, package_manager) |
| assert run_conan_install(minifi_options, package_manager) |
| assert run_cmake(minifi_options, package_manager) |
| return True |
| |
| |
| def main_menu(minifi_options: MinifiOptions, package_manager: PackageManager): |
| done = False |
| while not done: |
| main_menu_options = { |
| # All menu options' functions return True if the bootstrap should exit after execution |
| f"Build dir: {minifi_options.build_dir}": build_dir_menu, |
| f"Build type: {minifi_options.build_type.value}": build_type_menu, |
| f"Custom malloc: {minifi_options.custom_malloc.value if minifi_options.custom_malloc is not None else 'N/A'}": custom_malloc_menu, |
| "Build options": build_options_menu, |
| "Extension options": extension_options_menu, |
| "One click build": do_one_click_build, |
| "Step by step build": step_by_step_menu, |
| "Exit": lambda _options, _manager: True, |
| } |
| |
| questions = [ |
| inquirer.List( |
| "sub_menu", |
| message="Main Menu", |
| choices=[menu_option_name for menu_option_name in main_menu_options], |
| ), |
| ] |
| |
| main_menu_prompt = inquirer.prompt(questions) |
| if main_menu_prompt is None: |
| break |
| done = main_menu_options[main_menu_prompt["sub_menu"]]( |
| minifi_options, package_manager |
| ) |
| |
| |
| def build_type_menu( |
| minifi_options: MinifiOptions, _package_manager: PackageManager |
| ) -> bool: |
| questions = [ |
| inquirer.List( |
| "build_type", |
| message="Build type", |
| choices=minifi_options.build_type.possible_values, |
| ), |
| ] |
| |
| answers = inquirer.prompt(questions) |
| if answers is None: |
| return True |
| minifi_options.build_type.value = answers["build_type"] |
| minifi_options.save_option_state() |
| return False |
| |
| |
| def custom_malloc_menu( |
| minifi_options: MinifiOptions, _package_manager: PackageManager |
| ) -> bool: |
| if minifi_options.custom_malloc is None: |
| return False |
| questions = [ |
| inquirer.List( |
| "custom_malloc", |
| message="Custom malloc implementation (only jemalloc and mimalloc are provided via Conan)", |
| choices=minifi_options.custom_malloc.possible_values, |
| ), |
| ] |
| |
| answers = inquirer.prompt(questions) |
| if answers is None: |
| return True |
| minifi_options.custom_malloc.value = answers["custom_malloc"] |
| minifi_options.save_option_state() |
| return False |
| |
| |
| def build_dir_menu( |
| minifi_options: MinifiOptions, _package_manager: PackageManager |
| ) -> bool: |
| questions = [ |
| inquirer.Path( |
| "build_dir", message="Build directory", default=minifi_options.build_dir |
| ), |
| ] |
| answers = inquirer.prompt(questions) |
| if answers is None: |
| return True |
| minifi_options.build_dir = Path(answers["build_dir"]).resolve() |
| minifi_options.save_option_state() |
| return False |
| |
| |
| def extension_options_menu( |
| minifi_options: MinifiOptions, _package_manager: PackageManager |
| ) -> bool: |
| possible_values = [option_name for option_name in minifi_options.extension_options] |
| selected_values = [ |
| option.name |
| for option in minifi_options.extension_options.values() |
| if option.value == "ON" |
| ] |
| questions = [ |
| inquirer.Checkbox( |
| "options", |
| message="MiNiFi C++ Extension Options (space to select, enter to confirm)", |
| choices=possible_values, |
| default=selected_values, |
| ), |
| ] |
| |
| answers = inquirer.prompt(questions) |
| if answers is None: |
| return True |
| for extension_option in minifi_options.extension_options.values(): |
| if extension_option.name in answers["options"]: |
| extension_option.value = "ON" |
| else: |
| extension_option.value = "OFF" |
| |
| minifi_options.save_option_state() |
| return False |
| |
| |
| def build_options_menu( |
| minifi_options: MinifiOptions, _package_manager: PackageManager |
| ) -> bool: |
| possible_values = [option_name for option_name in minifi_options.build_options] |
| selected_values = [ |
| option.name |
| for option in minifi_options.build_options.values() |
| if option.value == "ON" |
| ] |
| questions = [ |
| inquirer.Checkbox( |
| "options", |
| message="MiNiFi C++ Build Options (space to select, enter to confirm)", |
| choices=possible_values, |
| default=selected_values, |
| ), |
| ] |
| |
| answers = inquirer.prompt(questions) |
| if answers is None: |
| return True |
| for build_option in minifi_options.build_options.values(): |
| if build_option.name in answers["options"]: |
| build_option.value = "ON" |
| else: |
| build_option.value = "OFF" |
| |
| minifi_options.save_option_state() |
| return False |
| |
| |
| def step_by_step_menu( |
| minifi_options: MinifiOptions, package_manager: PackageManager |
| ) -> bool: |
| done = False |
| while not done: |
| step_by_step_options = { |
| # All menu options' functions return True if the bootstrap should exit after execution |
| f"Build dir: {minifi_options.build_dir}": build_dir_menu, |
| "Install dependencies": install_dependencies, |
| "Run conan install": run_conan_install, |
| "Run cmake": run_cmake, |
| "Build": do_build, |
| "Package": do_package, |
| "Docker build": do_docker_build, |
| "Back": lambda _options, _manager: True, |
| } |
| questions = [ |
| inquirer.List( |
| "selection", |
| message="Step by step menu", |
| choices=[ |
| step_by_step_menu_option_name |
| for step_by_step_menu_option_name in step_by_step_options |
| ], |
| ), |
| ] |
| |
| step_by_step_prompt = inquirer.prompt(questions) |
| if step_by_step_prompt is None: |
| return True |
| step_by_step_options[step_by_step_prompt["selection"]]( |
| minifi_options, package_manager |
| ) |
| done = step_by_step_prompt["selection"] == "Back" |
| return False |