| # 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 pathlib |
| import platform |
| import subprocess |
| import sys |
| from enum import Enum |
| from typing import Dict, Set |
| |
| from distro import distro |
| |
| |
| class VsWhereLocation(Enum): |
| CHOCO = 1 |
| DEFAULT = 2 |
| |
| |
| def _query_yes_no(question: str, no_confirm: bool) -> bool: |
| valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} |
| |
| if no_confirm: |
| print("Running {} with noconfirm".format(question)) |
| return True |
| while True: |
| print("{} [y/n]".format(question), end=' ', flush=True) |
| choice = input().lower() |
| if choice in valid: |
| return valid[choice] |
| else: |
| print("Please respond with 'yes' or 'no' " "(or 'y' or 'n').") |
| |
| |
| def _run_command_with_confirm(command: str, no_confirm: bool) -> bool: |
| if _query_yes_no("Running {}".format(command), no_confirm): |
| return os.system(command) == 0 |
| |
| |
| class PackageManager(object): |
| def __init__(self, no_confirm): |
| self.no_confirm = no_confirm |
| pass |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| raise Exception("NotImplementedException") |
| |
| def install_compiler(self) -> str: |
| raise Exception("NotImplementedException") |
| |
| def ensure_environment(self): |
| pass |
| |
| def _install(self, dependencies: Dict[str, Set[str]], replace_dict: Dict[str, Set[str]], install_cmd: str) -> bool: |
| dependencies.update({k: v for k, v in replace_dict.items() if k in dependencies}) |
| dependencies = self._filter_out_installed_packages(dependencies) |
| dependencies_str = " ".join(str(value) for value_set in dependencies.values() for value in value_set) |
| if not dependencies_str or dependencies_str.isspace(): |
| return True |
| return _run_command_with_confirm(f"{install_cmd} {dependencies_str}", self.no_confirm) |
| |
| def _get_installed_packages(self) -> Set[str]: |
| raise Exception("NotImplementedException") |
| |
| def _filter_out_installed_packages(self, dependencies: Dict[str, Set[str]]): |
| installed_packages = self._get_installed_packages() |
| filtered_packages = {k: (v - installed_packages) for k, v in dependencies.items()} |
| for installed_package in installed_packages: |
| filtered_packages.pop(installed_package, None) |
| return filtered_packages |
| |
| def run_cmd(self, cmd: str) -> bool: |
| result = subprocess.run(f"{cmd}", shell=True, text=True) |
| return result.returncode == 0 |
| |
| |
| class BrewPackageManager(PackageManager): |
| def __init__(self, no_confirm): |
| PackageManager.__init__(self, no_confirm) |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| return self._install(dependencies=dependencies, |
| install_cmd="brew install", |
| replace_dict={"patch": set(), |
| "jni": {"maven"}}) |
| |
| def install_compiler(self) -> str: |
| self.install({"compiler": set()}) |
| return "" |
| |
| def _get_installed_packages(self) -> Set[str]: |
| result = subprocess.run(['brew', 'list'], text=True, capture_output=True, check=True) |
| lines = result.stdout.splitlines() |
| lines = [line.split('@', 1)[0] for line in lines] |
| return set(lines) |
| |
| |
| class AptPackageManager(PackageManager): |
| def __init__(self, no_confirm): |
| PackageManager.__init__(self, no_confirm) |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| return self._install(dependencies=dependencies, |
| install_cmd="sudo apt install -y", |
| replace_dict={"libarchive": {"liblzma-dev"}, |
| "lua": {"liblua5.1-0-dev"}, |
| "python": {"libpython3-dev"}, |
| "libusb": {"libusb-1.0-0-dev", "libusb-dev"}, |
| "libpng": {"libpng-dev"}, |
| "libpcap": {"libpcap-dev"}, |
| "jni": {"openjdk-8-jdk", "openjdk-8-source", "maven"}, |
| "gpsd": {"libgps-dev"}}) |
| |
| def _get_installed_packages(self) -> Set[str]: |
| result = subprocess.run(['dpkg', '--get-selections'], text=True, capture_output=True, check=True) |
| lines = [line.split('\t')[0] for line in result.stdout.splitlines()] |
| lines = [line.rsplit(':', 1)[0] for line in lines] |
| return set(lines) |
| |
| def install_compiler(self) -> str: |
| if distro.id() == "ubuntu" and int(distro.major_version()) < 22: |
| self.install({"compiler_prereq": {"apt-transport-https", "ca-certificates", "software-properties-common"}}) |
| _run_command_with_confirm("sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test", |
| no_confirm=self.no_confirm) |
| self.install({"compiler": {"build-essential", "g++-11"}}) |
| return "-DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11" |
| self.install({"compiler": {"g++"}}) |
| return "" |
| |
| |
| class DnfPackageManager(PackageManager): |
| def __init__(self, no_confirm): |
| PackageManager.__init__(self, no_confirm) |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| return self._install(dependencies=dependencies, |
| install_cmd="sudo dnf --enablerepo=crb install -y epel-release", |
| replace_dict={"gpsd": {"gpsd-devel"}, |
| "libpcap": {"libpcap-devel"}, |
| "lua": {"lua-devel"}, |
| "python": {"python3-devel"}, |
| "jni": {"java-1.8.0-openjdk", "java-1.8.0-openjdk-devel", "maven"}, |
| "libpng": {"libpng-devel"}, |
| "libusb": {"libusb-devel"}}) |
| |
| def _get_installed_packages(self) -> Set[str]: |
| result = subprocess.run(['dnf', 'list', 'installed'], text=True, capture_output=True, check=True) |
| lines = [line.split(' ')[0] for line in result.stdout.splitlines()] |
| lines = [line.rsplit('.', 1)[0] for line in lines] |
| return set(lines) |
| |
| def install_compiler(self) -> str: |
| self.install({"compiler": {"gcc-c++"}}) |
| return "" |
| |
| |
| class PacmanPackageManager(PackageManager): |
| def __init__(self, no_confirm): |
| PackageManager.__init__(self, no_confirm) |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| return self._install(dependencies=dependencies, |
| install_cmd="sudo pacman --noconfirm -S", |
| replace_dict={"jni": {"jdk8-openjdk", "maven"}}) |
| |
| def _get_installed_packages(self) -> Set[str]: |
| result = subprocess.run(['pacman', '-Qq'], text=True, capture_output=True, check=True) |
| return set(result.stdout.splitlines()) |
| |
| def install_compiler(self) -> str: |
| self.install({"compiler": {"gcc"}}) |
| return "" |
| |
| |
| def _get_vs_dev_cmd_path(vs_where_location: VsWhereLocation): |
| if vs_where_location == VsWhereLocation.CHOCO: |
| vs_where_path = "vswhere" |
| else: |
| vs_where_path = "%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe" |
| |
| vswhere_results = subprocess.run( |
| f"{vs_where_path} -products * " |
| f"-property installationPath " |
| f"-requires Microsoft.VisualStudio.Component.VC.ATL " |
| f"-version 17", |
| capture_output=True) |
| |
| for vswhere_result in vswhere_results.stdout.splitlines(): |
| possible_path = f"{vswhere_result.decode()}\\Common7\\Tools\\VsDevCmd.bat" |
| if os.path.exists(possible_path): |
| return f'"{possible_path}"' |
| return None |
| |
| |
| def _get_vs_dev_cmd(vs_where_location: VsWhereLocation) -> str: |
| vs_dev_path = _get_vs_dev_cmd_path(vs_where_location) |
| return f"{vs_dev_path} -arch=x64 -host_arch=x64" |
| |
| |
| def _get_activate_venv_path(): |
| return pathlib.Path(__file__).parent.resolve() / "venv" / "Scripts" / "activate.bat" |
| |
| |
| def _minifi_setup_env_str(vs_where_location: VsWhereLocation) -> str: |
| return f""" |
| call refreshenv |
| call {_get_vs_dev_cmd(vs_where_location)} |
| setlocal EnableDelayedExpansion |
| set PATH=!PATH:C:\\Strawberry\\c\\bin;=!;C:\\Program Files\\NASM; |
| endlocal & set PATH=%PATH% |
| set build_platform=x64 |
| IF "%VIRTUALENV%"=="" ( |
| echo already in venv |
| ) ELSE ( |
| {_get_activate_venv_path()} |
| ) |
| |
| """ |
| |
| |
| def _create_minifi_setup_env_batch(vs_where_location: VsWhereLocation): |
| with open(pathlib.Path(__file__).parent.resolve() / "build_environment.bat", "w") as f: |
| f.write(_minifi_setup_env_str(vs_where_location)) |
| |
| |
| class ChocolateyPackageManager(PackageManager): |
| def __init__(self, no_confirm): |
| PackageManager.__init__(self, no_confirm) |
| |
| def install(self, dependencies: Dict[str, Set[str]]) -> bool: |
| self._install(dependencies=dependencies, |
| install_cmd="choco install -y", |
| replace_dict={"lua": set(), |
| "python": set(), |
| "patch": set(), |
| "bison": set(), |
| "flex": set(), |
| "libarchive": set(), |
| "libpcap": set(), |
| "libpng": set(), |
| "gpsd": set(), |
| "automake": set(), |
| "autoconf": set(), |
| "libtool": set(), |
| "libusb": set(), |
| "make": set(), |
| "jni": {"openjdk", "maven"}, |
| "perl": {"strawberryperl", "NASM"}}) |
| return True |
| |
| def _get_installed_packages(self) -> Set[str]: |
| result = subprocess.run(['choco', 'list'], text=True, capture_output=True, check=True) |
| lines = [line.split(' ')[0] for line in result.stdout.splitlines()] |
| lines = [line.rsplit('.', 1)[0] for line in lines] |
| if os.path.exists("C:\\Program Files\\NASM"): |
| lines.append("NASM") # choco doesnt remember NASM |
| return set(lines) |
| |
| def _acquire_vswhere(self): |
| installed_packages = self._get_installed_packages() |
| if "vswhere" in installed_packages: |
| return VsWhereLocation.CHOCO |
| vswhere_default_path = "%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe" |
| if os.path.exists(vswhere_default_path): |
| return VsWhereLocation.DEFAULT |
| self.install({"vswhere": {"vswhere"}}) |
| return VsWhereLocation.CHOCO |
| |
| def install_compiler(self) -> str: |
| vs_where_loc = self._acquire_vswhere() |
| vs_dev_path = _get_vs_dev_cmd_path(vs_where_loc) |
| if not vs_dev_path: |
| self.install( |
| {"visualstudio2022buildtools": {'visualstudio2022buildtools --package-parameters "--wait --quiet ' |
| '--add Microsoft.VisualStudio.Workload.VCTools ' |
| '--add Microsoft.VisualStudio.Component.VC.ATL ' |
| '--includeRecommended"'}}) |
| return "" |
| |
| def run_cmd(self, cmd: str) -> bool: |
| env_bat_path = pathlib.Path(__file__).parent.resolve() / "build_environment.bat" |
| res = subprocess.run(f"{env_bat_path} & {cmd}", check=True, text=True) |
| |
| return res.returncode == 0 |
| |
| def ensure_environment(self): |
| _create_minifi_setup_env_batch(self._acquire_vswhere()) |
| |
| |
| def get_package_manager(no_confirm: bool) -> PackageManager: |
| platform_system = platform.system() |
| if platform_system == "Darwin": |
| return BrewPackageManager(no_confirm) |
| elif platform_system == "Linux": |
| distro_id = distro.id() |
| if distro_id == "ubuntu": |
| return AptPackageManager(no_confirm) |
| elif "arch" in distro_id or "manjaro" in distro_id: |
| return PacmanPackageManager(no_confirm) |
| elif "rocky" in distro_id: |
| return DnfPackageManager(no_confirm) |
| else: |
| sys.exit(f"Unsupported platform {distro_id} exiting") |
| elif platform_system == "Windows": |
| return ChocolateyPackageManager(no_confirm) |
| else: |
| sys.exit(f"Unsupported platform {platform_system} exiting") |