#!/usr/bin/env python
# coding: utf-8
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import argparse
import configparser
from dataclasses import dataclass
import getpass
import glob
import logging
from multiprocessing import Process
import os
from pathlib import Path
import re
import shlex
import shutil
import subprocess
import sys
import sysconfig
import tempfile
from typing import Tuple

from exceptions import ManifestException

HTTPS_PROXY_STR = 'https_proxy'

BINGO_DIST_NAME = 'openubmc-bingo'
HW_PYPI_INDEX = 'https://mirrors.huaweicloud.com/repository/pypi/simple'
DEFAULT_COMMAND_TIMEOUT = 600


@dataclass(frozen=True)
class PlatformSpec:
    """系统分支描述:init 仅识别系统(os-release ID),不校验版本/SP/架构。

    定位为 bootstrap 冷启动:把全新环境带到"bingo 自身可用",此后 bingo 已
    覆盖的流程(deps/SDK/升级)由 bingo 全权管理,init 不假设任何 bingo
    版本行为(兼容性不依赖版本号门槛拦截用户手动回退)。各阶段按本表分派:

    - 'legacy-full'(Ubuntu):维持既有完整链——deb 源配置(apt_config)、
      bingo deb 包安装、Conan/SDK/QEMU 等原行为全部保留,仅 Studio 移出;
    - 'minimal'(openEuler):冷启动最小集——dnf Bootstrap + pip 平台
      Wheel 安装 bingo,不执行 deps install 与 install_sdk(归 bingo 管理)。

    支持的验证基线(Ubuntu 24.04 x86_64 / openEuler 24.03 LTS-SP4 aarch64)
    在 README 声明;版本/SP/架构差异不在此拦截,交给下游(pip 选 Wheel、
    制品校验)自然暴露,避免 init 耦合架构。
    """

    os_id: str
    package_manager: str
    bootstrap_pkgs: Tuple[str, ...]
    bootstrap_mode: str


# Ubuntu 的 bootstrap_pkgs 为文档对齐用的最小集;bootstrap_mode='legacy-full'
# 实际复用 ubuntu_init()+install_dependencies() 全量链(保持存量场景行为不变)。
UBUNTU = PlatformSpec(
    os_id='ubuntu',
    package_manager='apt',
    bootstrap_pkgs=(
        'python3-pip',
        'python3-dev',
        'ca-certificates',
        'gcc',
        'g++',
        'pkgconf',
        'libdbus-1-dev',
        'libglib2.0-dev',
        'git',
    ),
    bootstrap_mode='legacy-full',
)

# openEuler 基础镜像实测不带 pip 与 sudo,均在清单内;nodejs 20.18.2/
# npm 10.8.2/lua/luarocks 为 everything 源实测可用版本(node/npm 即
# Ubuntu 链 n 20.18.0/npm@10.8.2 的目标版本,无需 n 链)。
OPENEULER = PlatformSpec(
    os_id='openeuler',
    package_manager='dnf',
    bootstrap_pkgs=(
        'python3-pip',
        'python3-devel',
        'ca-certificates',
        'gcc',
        'gcc-c++',
        'pkgconfig',
        'dbus-devel',
        'glib2-devel',
        'sudo',
        'git',
        'wget',
        'nodejs',
        'npm',
        'lua',
        'lua-devel',
        'luarocks',
    ),
    bootstrap_mode='minimal',
)

PLATFORM_SPECS = (UBUNTU, OPENEULER)


class Task(Process):
    @staticmethod
    def log_init():
        logger = logging.getLogger()
        logger.setLevel(logging.INFO)
        loglevel = os.environ.get('LOG')
        if loglevel is not None:
            formatter = logging.Formatter(
                fmt='[{asctime} {levelname} {filename}:{lineno} {funcName:4}] {message}', style='{'
            )
            if loglevel == 'debug':
                logger.setLevel(logging.DEBUG)
            elif loglevel == 'warn':
                logger.setLevel(logging.WARNING)
            elif loglevel == 'error':
                logger.setLevel(logging.ERROR)
        else:
            formatter = logging.Formatter(fmt='[{levelname}] {message}', style='{')
        handler = logging.StreamHandler(sys.stdout)
        handler.setFormatter(formatter)
        logger.handlers = []
        logger.addHandler(handler)

    @staticmethod
    def format_command(command, sudo=False):
        cmd_args: list = []
        if isinstance(command, list):
            cmd_args = command
        elif isinstance(command, str):
            cmd_args = shlex.split(command)
        else:
            raise ManifestException('Command with invalid instance')
        cmd_args[0] = shutil.which(cmd_args[0])
        if cmd_args[0] is None:
            raise ManifestException(f'{cmd_args}不存在,请检查命令或环境配置')
        sudo_cmd = shutil.which('sudo')
        if sudo and sudo_cmd and cmd_args[0] != sudo_cmd:
            cmd_args.insert(0, sudo_cmd)
        return cmd_args

    def chdir(self, path):
        logging.info(f'chdir: {path}')
        os.chdir(path)

    def run_command(self, command, stdout='', check=True, sudo=False, timeout=DEFAULT_COMMAND_TIMEOUT, **kwargs):
        """执行子进程命令。

        timeout=None 表示同步等待直至命令自然结束(供 SDK 安装等长时任务
        使用,避免 600 秒默认值误杀);其余调用点不传该参数,保持原默认行为。
        """
        command_echo = kwargs.get('command_echo', True)
        capture_output = kwargs.get('capture_output', False)
        if command_echo:
            logging.info(f'>>>> {command}')
        command = self.format_command(command, sudo)
        if stdout:
            return subprocess.run(command, check=check, timeout=timeout, stdout=stdout)
        else:
            return subprocess.run(command, check=check, timeout=timeout, capture_output=capture_output)


class WorkPrepare(Task):
    def __init__(self):
        super().__init__()
        parser = argparse.ArgumentParser(
            prog='openUBMC Development environment initialization', formatter_class=argparse.RawTextHelpFormatter
        )
        parser.add_argument(
            '-b', '--board_name', help='构建类型,支持openUBMC或oee,默认为openUBMC', required=False, default='openUBMC'
        )
        parser.add_argument('-user', '--openubmc_user', help='openUBMC社区用户名', required=False, default='')
        parser.add_argument(
            '-psw',
            '--openubmc_password',
            help='openUBMC社区用户密码,推荐使用交互式输入密码',
            required=False,
            default='',
        )
        parser.add_argument('--no_build', action='store_true', help='不进行sdk初始化')
        parsed_args, _ = parser.parse_known_args(sys.argv)

        self.home = os.environ['HOME']
        self.cwd = os.getcwd()
        # 非root用户设置环境变量
        path = os.environ.get('PATH')
        user_bin_path = f'{self.home}/.local/bin'
        self.need_add_user_bin_path = False
        if self.home.startswith('/home') and user_bin_path not in path:
            self.need_add_user_bin_path = True
            path = f'{user_bin_path}:' + path
            os.environ['PATH'] = path

        self.board_name = parsed_args.board_name
        self.user = parsed_args.openubmc_user
        self.password = parsed_args.openubmc_password
        self.no_build = parsed_args.no_build
        self.spec = None
        self.stage = 'start'
        self.summary = {'installed': [], 'skipped': []}

    def enter_stage(self, stage):
        """登记当前执行阶段,供失败摘要定位。"""
        self.stage = stage

    def record_installed(self, item):
        self.summary['installed'].append(item)

    def record_skipped(self, item):
        self.summary['skipped'].append(item)

    # ------------------------------------------------------------------ 探测段

    @staticmethod
    def read_os_release(path='/etc/os-release'):
        """解析 os-release 的机器可读字段,值去除单双引号。

        openEuler 的 ID 实测为驼峰 "openEuler",统一转小写后再参与匹配。
        """
        info = {}
        try:
            with open(path, encoding='utf-8') as fq:
                for line in fq:
                    if '=' not in line:
                        continue
                    key, value = line.strip().split('=', 1)
                    info[key] = value.strip('\'"')
        except OSError:
            pass
        return info

    def detect_system(self):
        """在修改系统前识别系统类型(仅 os-release ID),命中分支即继续。

        不校验版本/SP/架构:init 只做系统分支选择(不同阶段进入不同分支),
        架构与版本适配交给下游(pip 按宿主架构选 Wheel、制品校验)自然暴露;
        支持基线在 README 声明(openEuler 当前验证 24.03 LTS-SP4 aarch64)。
        """
        if os.environ.get('SUDO_USER') is not None:
            raise ManifestException('请直接执行,不支持以sudo环境执行脚本')
        os_info = self.read_os_release()
        if not os_info:
            raise ManifestException('无法从 /etc/os-release 获取系统信息,无法识别操作系统')
        os_id = os_info.get('ID', '').lower()
        for spec in PLATFORM_SPECS:
            if os_id == spec.os_id:
                self.spec = spec
                logging.info(f'检测到支持的系统: {spec.os_id}(包管理器: {spec.package_manager})')
                return spec
        supported = '、'.join(spec.os_id for spec in PLATFORM_SPECS)
        raise ManifestException(f'不支持的操作系统: {os_id!r};当前支持: {supported}(支持基线见 README)')

    # ------------------------------------------------------------------ 自举段

    def bootstrap_packages(self):
        """安装最小 Bootstrap 依赖。

        legacy-full 复用既有全量安装链保持 Ubuntu 存量场景行为不变;minimal 使用
        清单 + 平台包管理器,并在安装前做 sudo 可用性预检(openEuler 基础
        系统实测不带 sudo)。
        """
        if self.spec.bootstrap_mode == 'legacy-full':
            self.ubuntu_init()
            self.install_dependencies()
            self.record_installed('bootstrap: apt-get(既有依赖集)')
            return
        if os.getuid() != 0 and shutil.which('sudo') is None:
            raise ManifestException('当前非 root 且系统未安装 sudo,请先执行: dnf install -y sudo(或使用 root 运行)')
        packages = ' '.join(self.spec.bootstrap_pkgs)
        self.run_command(f'{self.spec.package_manager} install -y {packages}', sudo=(os.getuid() != 0))
        self.record_installed(f'bootstrap: {self.spec.package_manager}{len(self.spec.bootstrap_pkgs)} 个最小依赖)')

    # ------------------------------------------------------------------ bingo 安装

    def install_bingo(self):
        """冷启动安装 bingo,按系统分支:

        - Ubuntu(legacy-full):既有 deb 链原样——apt 源探测与配置
          (apt_config)、`apt install openubmc-bingo` deb 包安装、pip 兜底
          升级,与 pip 安装 conan 的原顺序一致(存量场景行为不变);
        - openEuler(minimal):pip 平台 Wheel(归一 PEP 668 探测)。
        """
        if self.spec.bootstrap_mode == 'legacy-full':
            self.install_bingo_legacy_apt()
            return
        self.pip_install_bingo()

    def install_bingo_legacy_apt(self):
        """Ubuntu 既有 bingo 安装链(行为原样保留:deb 源 + deb 包 + pip)。"""
        stop_ret = self.run_command('apt policy openubmc-bingo', capture_output=True)
        if len(stop_ret.stdout) == 0:
            self.apt_config()
        try:
            self.run_command('pip install conan>=2.0.0')
            self.run_command('apt install -y openubmc-bingo --upgrade', sudo=True)
            self.run_command('pip install openubmc-bingo --upgrade')
        except Exception:
            logging.info('openubmc-bingo下载失败,请检查apt源是否配置成功')
        self.record_installed('bingo(apt deb 源 + deb 包 + pip 升级,既有链)')

    def bingo_requirement(self):
        """从 .bmcgo/config 的 [bingo] version 解析 pip requirement。

        该配置是 manifest 运行兼容约束的唯一来源,不在 init.py 中维护第二份版本。
        """
        cp = configparser.ConfigParser()
        cp.read(os.path.join(self.cwd, '.bmcgo', 'config'), encoding='utf-8')
        raw = cp.get('bingo', 'version', fallback='').strip()
        requirement = raw.strip('[]').strip()
        if not requirement:
            raise ManifestException(f'无法从 .bmcgo/config 解析 [bingo] version,读取值: {raw!r}')
        return requirement

    def pip_install_bingo(self):
        """openEuler 冷启动:pip 安装 Bingo 平台 Wheel(归一方式)。

        是否追加 --break-system-packages 由运行时探测 EXTERNALLY-MANAGED
        标记决定(实测 Ubuntu 24.04 存在、openEuler 24.03 不存在),不按
        发行版硬编码;平台 Wheel 的架构选择交给 pip 按宿主架构完成——架构
        不匹配时 pip 直接失败,即冷启动的自然边界。
        """
        requirement = f'{BINGO_DIST_NAME}{self.bingo_requirement()}'
        command = [sys.executable, '-m', 'pip', 'install', '--upgrade', requirement, '--index-url', HW_PYPI_INDEX]
        pep668 = Path(sysconfig.get_path('stdlib')) / 'EXTERNALLY-MANAGED'
        if pep668.exists():
            command.append('--break-system-packages')
            logging.info('检测到 PEP 668 外部托管环境,pip 追加 --break-system-packages')
        self.run_command(command)
        # 不用同进程 importlib.metadata 回读版本:Python 3.11 的 FastPath
        # lru_cache 对 site-packages 扫描结果永不失效(mtime 失效是 3.12
        # 引入),安装前建立的空目录缓存会让刚装好的包查不到(实测命中)。
        # 版本信息以 pip 安装输出为准,可运行性由 verify_bingo 的子进程
        # `bingo --version` 验证。
        self.record_installed(f'bingo(pip 安装,{requirement})')

    def verify_bingo(self):
        """冷启动完成判据(轻量):bingo CLI 可运行。

        深度安装完整性(payload/packages marker、signer 架构)属 Bingo 自身
        范畴且涉及其内部布局,init 不硬编码复核;端到端环境可用性由 bingo
        覆盖的流程(deps/SDK/构建)自行验证。子进程执行 bingo CLI——其
        元数据可见性与安装进程无关。
        """
        self.run_command('bingo --version')
        logging.info('bingo CLI 可运行(冷启动完成判据)')

    # ------------------------------------------------------------------ 编排段

    def install_sdk(self):
        """调用 Bingo 安装 bmc_sdk:不设外层超时,失败上抛(不降级为 warning)。"""
        logging.info('Start installing sdk:')
        self.chdir(self.cwd)
        # SDK 下载与安装耗时受网络与制品体积影响,timeout=None 同步等待 Bingo
        # 自身结束,避免 600 秒默认值误杀 sudo/root 子进程链。
        self.run_command(f'bingo build -t install_sdk -b {self.board_name} -ucc', timeout=None)
        self.record_installed(f'bmc_sdk: board={self.board_name}')

    def install_qemu_chain(self):
        """Ubuntu 既有 QEMU 安装链(原行为保留)。"""
        logging.info('开始安装qemu的deb包和qemu依赖环境: ')
        self.install_qemu_compile_dependencies()
        self.install_qemu_runtime_dependencies()
        self.install_qemu()
        self.record_installed('QEMU 环境')

    def install_dev_tools(self):
        """pre-commit 工具(node/stylua/luacheck)两平台必装。

        node/lua 工具是提交代码 pre-commit 钩子的依赖,属开发环境必备
        (bootstrap 性质);Studio 两平台均不自动安装(README 手工指引)。
        """
        self.install_node()
        self.install_lua_tools()
        self.record_installed('pre-commit 工具: node/stylua/luacheck')
        self.record_skipped('BMC Studio: 已移出默认初始化,请参考 README 手工安装指引')

    def print_summary(self):
        installed = '、'.join(self.summary['installed']) or '无'
        skipped = '、'.join(self.summary['skipped']) or '无'
        logging.info('========== 初始化摘要 ==========')
        logging.info(f'系统: {self.spec.os_id}{self.spec.bootstrap_mode})')
        logging.info(f'board: {self.board_name}')
        logging.info(f'installed: {installed}')
        logging.info(f'skipped: {skipped}')
        logging.info('================================')

    def print_failure_summary(self, exc):
        """失败摘要:失败阶段、已完成项与重试建议,不输出笼统的失败信息。"""
        installed = '、'.join(self.summary['installed']) or '无'
        logging.error('========== 初始化失败 ==========')
        logging.error(f'失败阶段: {self.stage}')
        logging.error(f'已完成: {installed}')
        logging.error(f'原因: {exc}')
        logging.error('建议: 修复上述问题后重新执行 python3 init.py(各阶段可安全重跑)')
        logging.error('================================')

    # -------------------------------------------------------------- 公共配置段

    def proxy_check(self):
        https_proxy = os.environ.get(HTTPS_PROXY_STR)
        if https_proxy:
            logging.warning('检测到环境设置了https_proxy代理,可能导致初始化失败,请确认是否关闭代理运行?(Y/N)')
            if input().upper() == 'Y':
                os.environ[HTTPS_PROXY_STR] = ''
                os.environ[HTTPS_PROXY_STR.upper()] = ''

    def pip_config(self):
        """pip 镜像统一华为云;两平台共用(openEuler 无 PEP 668 标记,相关配置无副作用)。

        pip config set 恒要求恰好 name/value 两个位置参数(值不能内嵌引号,
        否则 pip 报 unexpected number of arguments)。
        """
        logging.info('Start configuring pip:')
        pip = [sys.executable, '-m', 'pip', 'config', 'set']
        self.run_command(pip + ['global.index-url', HW_PYPI_INDEX])
        self.run_command(pip + ['global.trusted-host', 'mirrors.huaweicloud.com'])
        pep668 = Path(sysconfig.get_path('stdlib')) / 'EXTERNALLY-MANAGED'
        if pep668.exists():
            self.run_command(pip + ['global.break-system-packages', 'true'])

    # -------------------------------------------------------- 既有 Ubuntu 链路

    def ubuntu_init(self):
        self.run_command('ln -snf /bin/bash /bin/sh', sudo=True)
        logging.info('Initial ubuntu env')
        self.run_command(
            f"sed -i 's@/archive.ubuntu.com@/mirrors.huaweicloud.com@g' /etc/apt/sources.list.d/ubuntu.sources",
            sudo=True,
            check=False,
        )
        self.run_command(
            f"sed -i 's@/security.ubuntu.com@/mirrors.huaweicloud.com@g' /etc/apt/sources.list.d/ubuntu.sources",
            sudo=True,
            check=False,
        )
        self.run_command(
            f"sed -i 's@/azure.mirrors.huaweicloud.com@/azure.archive.ubuntu.com@g' "
            f'/etc/apt/sources.list.d/ubuntu.sources',
            sudo=True,
            check=False,
        )
        self.run_command('apt-get update', sudo=True)

    def install_dependencies(self):
        logging.info('Start installing the dependencies:')
        self.run_command(
            f'apt-get install -y python3 python3-pip python3-dev git wget curl ca-certificates \
            ipmitool dbus-x11 libdbus-1-dev dbus \
            pkgconf libglib2.0-dev gcc-9 g++-9 unzip npm',
            sudo=True,
        )
        self.run_command(
            f'update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 20 \
            --slave /usr/bin/g++ g++ /usr/bin/g++-9 --slave /usr/bin/gcov gcov /usr/bin/gcov-9',
            sudo=True,
        )
        self.run_command('apt remove -y python3-urllib3 python3-distro', sudo=True)
        self.run_command('apt autoremove -y', sudo=True)

    def node_version(self):
        """返回已装 node 主次版本元组(如 (20, 18));未装返回 None。"""
        if shutil.which('node') is None:
            return None
        ret = self.run_command('node -v', capture_output=True, check=False)
        matched = re.match(r'v(\d+)\.(\d+)', ret.stdout.decode('utf-8', errors='ignore'))
        return (int(matched.group(1)), int(matched.group(2))) if matched else None

    def install_node(self):
        logging.info('Start installing node:')
        _ = self.run_command('npm config set registry https://registry.npmmirror.com', sudo=True)
        # openEuler 经 dnf 安装 nodejs 20.18/npm 10.8(everything 源实测即目标
        # 版本),版本满足则跳过 n 版本管理链;Ubuntu apt npm 附带 node 较旧,
        # 仍走既有 n 链升级到 20.18.0。
        version = self.node_version()
        if version is not None and version >= (20, 18):
            logging.info(f'node v{version[0]}.{version[1]} 已满足要求,跳过 n 版本管理链')
            return
        self.run_command('npm install -g n', sudo=True)
        self.run_command('n 20.18.0', sudo=True)
        self.run_command('npm install -g npm@10.8.2', sudo=True)

    def install_lua_tools(self):
        logging.info('Start installing pre-commit tools (stylua/luacheck):')

        # 1. 安装 stylua
        if shutil.which('stylua') is None:
            logging.info('stylua not found, starting installation:')
            self.run_command(
                'npm install -g @johnnymorganz/stylua-bin --registry=https://mirrors.huaweicloud.com/repository/npm/',
                sudo=True,
            )
            self.run_command('stylua --version')
        else:
            logging.info('stylua is already installed, skipping.')

        # 2. 安装 luacheck
        if shutil.which('luacheck') is None:
            logging.info('luacheck not found, starting installation:')
            if self.spec.bootstrap_mode == 'legacy-full':
                self.run_command('apt update', sudo=True)
                self.run_command('apt install -y luarocks lua5.1 liblua5.1-dev', sudo=True)
            # openEuler 的 lua/luarocks/lua-devel 已在 Bootstrap 清单安装(dnf)。

            rocks = ['argparse-0.7.2-1', 'luafilesystem-1.9.0-1', 'luacheck-1.2.0-1']

            # 使用 with 语句管理临时目录,确保退出时自动清理
            with tempfile.TemporaryDirectory() as tmpdir:
                self.chdir(tmpdir)
                try:
                    logging.info('Downloading luacheck dependencies from dailybuild.openubmc.cn:')

                    download_urls = ' '.join(
                        f'https://dailybuild.openubmc.cn/pre-commit/{rock}.src.rock' for rock in rocks
                    )
                    self.run_command(f'wget {download_urls}')

                    for rock in rocks:
                        self.run_command(f'luarocks install {rock}.src.rock', sudo=True)
                finally:
                    # 无论成功失败都切回原工作目录,避免停留在已清理的临时目录
                    self.chdir(self.cwd)
        else:
            logging.info('luacheck is already installed, skipping.')

        logging.info('Pre-commit tools (stylua/luacheck) installation completed.')

    def apt_config(self):
        key_id = 'BF3B2D407FDEDEA0DC91CC9412640FBCB175287A'
        keyrings_dir = '/usr/share/keyrings'
        gpg_path = f'{keyrings_dir}/openubmc.gpg'
        apt_source = (
            f'deb [arch=amd64 signed-by={gpg_path}] '
            f'            https://openubmc-apt-repo.obs.cn-north-4.myhuaweicloud.com/Ubuntu/ noble main'
        )
        sources_list = '/etc/apt/sources.list.d/openubmc.list'
        try:
            self.run_command(f'mkdir -p {keyrings_dir}', sudo=True)
            self.run_command(f'gpg --keyserver keyserver.ubuntu.com --recv-keys {key_id}')
            with tempfile.NamedTemporaryFile() as tmp_f:
                tmp_gpg_path = tmp_f.name
                with open(tmp_gpg_path, 'wb') as f:
                    self.run_command(f'gpg --export {key_id}', check=True, stdout=f)
                self.run_command(f'cp {tmp_gpg_path} {gpg_path}', sudo=True)
            self.run_command(f'chmod 644 {gpg_path}', sudo=True)
            logging.info(f'Writing APT source to {sources_list}...')
            with tempfile.NamedTemporaryFile() as tmp_f:
                tmp_list_path = tmp_f.name
                with open(tmp_list_path, 'w') as f:
                    f.write(apt_source + '\n')
                self.run_command(f'cp {tmp_list_path} {sources_list}', sudo=True)
            self.run_command(f'chmod 644 {sources_list}', sudo=True)
            self.run_command('apt update', sudo=True)
        except subprocess.CalledProcessError as e:
            logging.error(f'Command failed: {" ".join(e.cmd)}\nError: {e.stderr}')
        except Exception as e:
            logging.error(f'Error: {str(e)}')

    def conan_config(self):
        logging.info('Start configuring conan:')
        self.run_command('conan profile detect --force')
        _ = self.run_command("conan remote remove '*'", check=False)
        _ = self.run_command(
            "conan remote add openubmc_opensource 'https://conan.openubmc.cn/openubmc_opensource' --insecure --force"
        )
        if not self.user:
            conan_usr = input('请输入conan仓的用户名: ').strip()
        else:
            conan_usr = self.user
        if not self.password:
            conan_psw = getpass.getpass(prompt='请输入conan仓的密码: ')
        else:
            conan_psw = self.password
        _ = self.run_command(f'conan remote login openubmc_opensource {conan_usr} -p {conan_psw}', command_echo=False)

    def install_qemu(self):
        stop_ret = self.run_command('apt policy openubmc-qemu', capture_output=True)
        if len(stop_ret.stdout) == 0:
            self.apt_config()
        try:
            self.run_command('apt install -y openubmc-qemu --upgrade', sudo=True)
        except Exception:
            logging.info('openubmc-qemu下载失败, 请检查apt源是否配置成功')

    def install_qemu_compile_dependencies(self):
        self.run_command(
            f'apt-get install -y make bzip2 pkg-config libslirp-dev libpixman-1-dev ninja-build \
            libcap-ng-dev libfdt-dev libattr1-dev',
            sudo=True,
        )

    def install_qemu_runtime_dependencies(self):
        self.run_command(
            'cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak', sudo=True
        )
        self.run_command("sed -i 's/Suites:/Suites: plucky/g' /etc/apt/sources.list.d/ubuntu.sources", sudo=True)
        self.run_command('apt-get update', sudo=True)
        self.run_command('apt-get install -y libcapstone5', sudo=True)
        self.run_command('rm /etc/apt/sources.list.d/ubuntu.sources', sudo=True)
        self.run_command(
            'mv /etc/apt/sources.list.d/ubuntu.sources.bak /etc/apt/sources.list.d/ubuntu.sources', sudo=True
        )
        self.run_command('apt-get update', sudo=True)

    def check_path(self):
        if self.need_add_user_bin_path:
            logging.warning(
                '检测到当前用户为非root用户,且未配置环境变量,请配置环境变量,'
                '如不配置环境变量可能导致bingo命令无法运行!'
            )
            logging.warning('参考命令:')
            logging.warning('echo \'export PATH="$HOME/.local/bin:$PATH"\' >> ~/.bashrc')
            logging.warning('source ~/.bashrc')
            logging.warning('请确认已知悉,输入Y:')
            if input().upper() == 'Y':
                return

    def run(self):
        try:
            self.enter_stage('proxy-check')
            self.proxy_check()
            # ---- 探测:仅识别系统,不校验版本/SP/架构(避免耦合架构)----
            self.enter_stage('detect-system')
            self.detect_system()
            # ---- 自举:系统依赖(legacy-full=既有全量链 / minimal=dnf 最小集)----
            self.enter_stage('bootstrap-packages')
            self.bootstrap_packages()
            self.enter_stage('pip-config')
            self.pip_config()
            # ---- bingo 冷启动安装(Ubuntu=既有 deb 链 / openEuler=pip Wheel)----
            self.enter_stage('install-bingo')
            self.install_bingo()
            self.enter_stage('verify-bingo')
            self.verify_bingo()
            self.enter_stage('conan-config')
            self.conan_config()
            # ---- 工具链:pre-commit 工具两平台必装 ----
            self.enter_stage('dev-tools')
            self.install_dev_tools()
            # ---- 系统分支收尾:legacy-full 维持既有完整链,minimal 止于冷启动 ----
            if self.spec.bootstrap_mode == 'legacy-full':
                # Ubuntu:SDK/QEMU 为原行为(仅 Studio 移出);timeout=None 防误杀
                if not self.no_build:
                    self.enter_stage('install-sdk')
                    self.install_sdk()
                self.enter_stage('qemu')
                self.install_qemu_chain()
            else:
                # openEuler 冷启动边界:deps install / install_sdk 归 bingo 全权
                # 管理(用户自行执行),本脚本不调用、不假设 bingo 版本行为
                self.record_skipped('bingo deps install / install_sdk: openEuler 冷启动不执行,由 bingo 管理')
            self.check_path()
            self.print_summary()
            logging.info('openUBMC开发者环境初始化完成!')
        except Exception as exc:
            if os.environ.get('LOG'):
                import traceback

                logging.error(traceback.format_exc())
            self.print_failure_summary(exc)
            return -1
        return 0


if __name__ == '__main__':
    Task.log_init()
    wk = WorkPrepare()
    sys.exit(wk.run())