From 2d8b9d4e86f64c723518d96aa1d8396d4084ffb8 Mon Sep 17 00:00:00 2001 From: Liu Zhangjian Date: Wed, 27 Nov 2024 17:49:45 +0800 Subject: [PATCH] refactor: [toolchain] migrate toolchain detection from shell to python Refactored the toolchain detection mechanism by replacing the shell script with a Python implementation for better maintainability and extensibility. - Replaced toolchain.sh with main.py as the detection script - Updated file paths and script execution logic in toolchain.cpp Log: refactor toolchain detection implementation --- src/common/toolchain/toolchain.cpp | 38 +-- src/scripts/CMakeLists.txt | 3 + src/scripts/toolchain.sh | 281 ------------------ src/scripts/toolchain/base_toolchain.py | 51 ++++ src/scripts/toolchain/main.py | 75 +++++ .../toolchain/parsers/gcc_toolchain.py | 70 +++++ .../toolchain/parsers/java_toolchain.py | 41 +++ .../toolchain/parsers/python_toolchain.py | 39 +++ .../toolchain/parsers/simple_toolchain.py | 21 ++ src/scripts/toolchain/toolchain_config.json | 63 ++++ src/scripts/toolchain/toolchain_factory.py | 73 +++++ src/services/option/toolchaindata.cpp | 11 +- 12 files changed, 459 insertions(+), 307 deletions(-) delete mode 100755 src/scripts/toolchain.sh create mode 100644 src/scripts/toolchain/base_toolchain.py create mode 100644 src/scripts/toolchain/main.py create mode 100644 src/scripts/toolchain/parsers/gcc_toolchain.py create mode 100644 src/scripts/toolchain/parsers/java_toolchain.py create mode 100644 src/scripts/toolchain/parsers/python_toolchain.py create mode 100644 src/scripts/toolchain/parsers/simple_toolchain.py create mode 100644 src/scripts/toolchain/toolchain_config.json create mode 100644 src/scripts/toolchain/toolchain_factory.py diff --git a/src/common/toolchain/toolchain.cpp b/src/common/toolchain/toolchain.cpp index 81581f1c9..78eedee7a 100644 --- a/src/common/toolchain/toolchain.cpp +++ b/src/common/toolchain/toolchain.cpp @@ -15,21 +15,21 @@ #include namespace toolchains { -const QString K_SCIRPTNAME{"toolchain.sh"}; -const QString K_TOOLCHAINFILE{"toolchains.support"}; -const QString K_VERSION{"version"}; -const QString K_HOSTK_TOOLCHAINFILEK_TOOLCHAINFILEK_TOOLCHAINFILE_OS{"host_os"}; -const QString K_HOST_ARCH{"host_arch"}; -const QString K_HOST_KERNEL{"host_kernel"}; -const QString K_TOOLCHAINS{"toolchains"}; -const QString K_TOOLCHAIN_NAME{"toolchain_name"}; -const QString K_TOOLCHAIN_ABI{"toolchain_abi"}; -const QString K_TOOLCHAIN_PREFIX{"toolchain_prefix"}; -const QString K_TOOLCHAIN_PATH{"toolchain_path"}; -const QString K_TOOLCHAIN_C_COMPILER{"toolchain_c_compiler"}; -const QString K_TOOLCHAIN_CXX_COMPILER{"toolchain_cxx_compiler"}; -const QString K_TOOLCHAIN_DEBUGGER{"toolchain_debugger"}; -} // namespace toolchain +const QString K_SCIRPTNAME { "toolchain/main.py" }; +const QString K_TOOLCHAINFILE { "toolchains.json" }; +const QString K_VERSION { "version" }; +const QString K_HOSTK_TOOLCHAINFILEK_TOOLCHAINFILEK_TOOLCHAINFILE_OS { "host_os" }; +const QString K_HOST_ARCH { "host_arch" }; +const QString K_HOST_KERNEL { "host_kernel" }; +const QString K_TOOLCHAINS { "toolchains" }; +const QString K_TOOLCHAIN_NAME { "toolchain_name" }; +const QString K_TOOLCHAIN_ABI { "toolchain_abi" }; +const QString K_TOOLCHAIN_PREFIX { "toolchain_prefix" }; +const QString K_TOOLCHAIN_PATH { "toolchain_path" }; +const QString K_TOOLCHAIN_C_COMPILER { "toolchain_c_compiler" }; +const QString K_TOOLCHAIN_CXX_COMPILER { "toolchain_cxx_compiler" }; +const QString K_TOOLCHAIN_DEBUGGER { "toolchain_debugger" }; +} // namespace toolchain bool toolchains::generatGlobalFile() { @@ -37,14 +37,14 @@ bool toolchains::generatGlobalFile() if (!QFileInfo(script).isFile()) return false; - QString result = CustomPaths::user(CustomPaths::Configures) + QDir::separator() + toolchains::K_TOOLCHAINFILE; - ProcessUtil::execute(script, {result}, [=](const QByteArray &out){ + QString outputFile = CustomPaths::user(CustomPaths::Configures) + QDir::separator() + toolchains::K_TOOLCHAINFILE; + QStringList args { script, "-o", outputFile }; + ProcessUtil::execute("python3", args, [=](const QByteArray &out) { qInfo() << out; }); - if (QFile(result).exists()) + if (QFile(outputFile).exists()) return true; return false; } - diff --git a/src/scripts/CMakeLists.txt b/src/scripts/CMakeLists.txt index 464b55746..76bcc61ad 100644 --- a/src/scripts/CMakeLists.txt +++ b/src/scripts/CMakeLists.txt @@ -24,3 +24,6 @@ install(DIRECTORY rag/ install(DIRECTORY compiledb/ DESTINATION "${LIBRARY_INSTALL_PREFIX}/scripts/compiledb") + +install(DIRECTORY compiledb/ + DESTINATION "${LIBRARY_INSTALL_PREFIX}/scripts/toolchain") diff --git a/src/scripts/toolchain.sh b/src/scripts/toolchain.sh deleted file mode 100755 index 0ee0a268f..000000000 --- a/src/scripts/toolchain.sh +++ /dev/null @@ -1,281 +0,0 @@ -#!/bin/bash - -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -# -# SPDX-License-Identifier: GPL-3.0-or-later -export TOOLCHAINS=$1 - -echo "[" > $TOOLCHAINS - -search_path="/usr/bin" -prefix="x86_64-linux-gnu- aarch64-linux-gnu- mips64el-linux-gnuabi64-" -################################################################################## - -# probe c compilers -probe_c_compilers() -{ - C_COMPILERS=$(find $search_path -name 'gcc' \ - -o -name 'gcc-[1-9]*' \ - -o -name 'x86_64-linux-gnu-gcc' \ - -o -name 'x86_64-linux-gnu-gcc-[1-9]*' \ - -o -name 'aarch64-linux-gnu-gcc' \ - -o -name 'aarch64-linux-gnu-gcc-[1-9]*' \ - -o -name 'mips64el-linux-gnuabi64-gcc' \ - -o -name 'mips64el-linux-gnuabi64-gcc-[1-9]*' \ - -o -name 'clang-[1-9]*' - ) - - echo "{\"C compilers\": [" >> $TOOLCHAINS - for compiler in ${C_COMPILERS[@]} clang; do - echo "{" >> $TOOLCHAINS - name=$(basename $compiler) - if [ $name = "clang" ]; then - path=$(which clang) - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - else - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$compiler\"" >> $TOOLCHAINS - echo "}," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe c++ compilers -CXX_COMPILERS="g++ x86_64-linux-gnu-g++ aarch64-linux-gnu-g++ mips64el-linux-gnuabi64-g++ g++-8 x86_64-linux-gnu-g++-8 aarch64-linux-gnu-g++-8 mips64el-linux-gnuabi64-g++-8 clang++" -probe_cxx_compilers() -{ - CXX_COMPILERS=$(find $search_path -name 'g++' \ - -o -name 'gcc-[1-9]*' \ - -o -name 'x86_64-linux-gnu-g++' \ - -o -name 'x86_64-linux-gnu-g++-[1-9]*' \ - -o -name 'aarch64-linux-gnu-g++' \ - -o -name 'aarch64-linux-gnu-g++-[1-9]*' \ - -o -name 'mips64el-linux-gnuabi64-g++' \ - -o -name 'mips64el-linux-gnuabi64-g++-[1-9]*' \ - -o -name 'clang++-[1-9]*' - ) - - echo "{\"C++ compilers\": [" >> $TOOLCHAINS - for compiler in ${CXX_COMPILERS[@]} clang++; do - echo "{" >> $TOOLCHAINS - name=$(basename $compiler) - if [ $compiler = clang++ ]; then - path=$(which clang++) - echo "\"name\":\"clang++\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - else - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$compiler\"" >> $TOOLCHAINS - echo "}," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe gdb/lldb - -probe_debuggers() -{ - DEBUGGERS=($(find $search_path -name gdb \ - -o -name lldb-[1-9]* -o -name lldb - )) - echo "{\"C/C++ debuggers\": [" >> $TOOLCHAINS - count=${#DEBUGGERS[@]} - for ((i=0;i<$count;i++)) do - debugger=${DEBUGGERS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $debugger) - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$debugger\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe cmake -probe_build_systems() -{ - CMAKE_VERSIONS=($(find $search_path -name 'cmake')) - echo "{\"C/C++ build systems\": [" >> $TOOLCHAINS - count=${#CMAKE_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - version=${CMAKE_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $version) - path=$version - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe ninja -probe_ninja() -{ - NINJA_VERSIONS=($(find $search_path -name 'ninja')) - echo "{\"Ninja\": [" >> $TOOLCHAINS - count=${#NINJA_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - version=${NINJA_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $version) - path=$version - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe clangd -probe_lsp_servers() -{ - LSP_SERVERS=$(find $search_path -name clangd-[1-9]*) - echo "{\"C/C++ LSP Servers\": [" >> $TOOLCHAINS - for lsp in ${LSP_SERVERS[@]} clangd; do - echo "{" >> $TOOLCHAINS - name=$(basename $lsp) - if [ $lsp = clangd ]; then - path=$(which clangd) - echo "\"name\":\"clangd\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - else - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$lsp\"" >> $TOOLCHAINS - echo "}," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe jdk -probe_jdk() -{ - JDK_VERSIONS=($(find $search_path -name 'java')) - echo "{\"JDK\": [" >> $TOOLCHAINS - count=${#JDK_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - jdkversion=${JDK_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(java -version 2>&1 |awk 'NR==1{gsub(/"/,"");print $1 " " $3}') - path=$jdkversion - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe maven -probe_maven() -{ - MAVEN_VERSIONS=($(find $search_path -name 'mvn')) - echo "{\"Maven\": [" >> $TOOLCHAINS - count=${#MAVEN_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - mavenversion=${MAVEN_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $mavenversion) - path=$mavenversion - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe gradle -probe_gradle() -{ - GRADLE_VERSIONS=($(find $search_path -name 'gradle')) - echo "{\"Gradle\": [" >> $TOOLCHAINS - count=${#GRADLE_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - gradleversion=${GRADLE_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $gradleversion) - path=$gradleversion - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -# probe python -probe_python() -{ - PYTHON_VERSIONS=($(find $search_path -name 'python[0-9]*\.*[0-9]*' | grep -P 'python[0-9]*\.*[0-9]*$')) - echo "{\"Python\": [" >> $TOOLCHAINS - count=${#PYTHON_VERSIONS[@]} - for ((i=0;i<$count;i++)) do - pythonversion=${PYTHON_VERSIONS[i]} - echo "{" >> $TOOLCHAINS - name=$(basename $pythonversion) - path=$pythonversion - echo "\"name\":\"$name\"," >> $TOOLCHAINS - echo "\"path\":\"$path\"" >> $TOOLCHAINS - echo "}" >> $TOOLCHAINS - last=$[$count-1] - if [ $i -ne $last ]; then - echo "," >> $TOOLCHAINS - fi - done - echo "]}" >> $TOOLCHAINS -} - -############################################################ -# main entry -probe_c_compilers -echo "," >> $TOOLCHAINS -probe_cxx_compilers -echo "," >> $TOOLCHAINS -probe_debuggers -echo "," >> $TOOLCHAINS -probe_build_systems -echo "," >> $TOOLCHAINS -probe_lsp_servers -echo "," >> $TOOLCHAINS -probe_jdk -echo "," >> $TOOLCHAINS -probe_maven -echo "," >> $TOOLCHAINS -probe_gradle -echo "," >> $TOOLCHAINS -probe_python -echo "," >> $TOOLCHAINS -probe_ninja - -echo "]" >> $TOOLCHAINS - -exit 0 diff --git a/src/scripts/toolchain/base_toolchain.py b/src/scripts/toolchain/base_toolchain.py new file mode 100644 index 000000000..78c583cfb --- /dev/null +++ b/src/scripts/toolchain/base_toolchain.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from abc import ABC, abstractmethod +from typing import List, Dict +import os +import glob +from dataclasses import dataclass + +@dataclass +class ToolchainInfo: + name: str + path: str + +class BaseToolchain(ABC): + def __init__(self): + self._executables: List[str] = [] + + def find_tools(self, tool_name: str) -> List[str]: + tools = [] + paths = os.environ.get("PATH", "").split(os.pathsep) + + priority_paths = ['/usr/bin', '/bin'] + other_paths = [p for p in paths if p not in priority_paths] + scan_paths = priority_paths + other_paths + + for path in scan_paths: + if not os.path.exists(path): + continue + matches = glob.glob(os.path.join(path, tool_name)) + for match in matches: + if os.access(match, os.X_OK | os.F_OK): + tools.append(match) + return self.auto_detect_toolchains(tools) + + def auto_detect_toolchains(self, compiler_paths: List[str]) -> List[str]: + result = [] + for compiler_path in compiler_paths: + already_exists = False + for existing_tc in result: + already_exists = os.lstat(existing_tc).st_ino == os.lstat(compiler_path).st_ino + if already_exists: + break + if not already_exists: + result.append(compiler_path) + return result + + @abstractmethod + def scan(self) -> List[ToolchainInfo]: + pass \ No newline at end of file diff --git a/src/scripts/toolchain/main.py b/src/scripts/toolchain/main.py new file mode 100644 index 000000000..da9de1e92 --- /dev/null +++ b/src/scripts/toolchain/main.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import json +import logging +import argparse +import os +from typing import Dict, List +from base_toolchain import ToolchainInfo +from toolchain_factory import ToolchainFactory + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +class ToolchainScanner: + def __init__(self, config_file: str): + self.factory = ToolchainFactory(config_file) + self.toolchains: Dict[str, List[ToolchainInfo]] = { + t: [] for t in self.factory.get_supported_types() + } + + def scan_system(self) -> None: + for toolchain_type in self.toolchains.keys(): + try: + scanner = self.factory.create_toolchain(toolchain_type) + self.toolchains[toolchain_type] = scanner.scan() + logger.info(f"Found {len(self.toolchains[toolchain_type])} {toolchain_type}") + except Exception as e: + logger.error(f"Error scanning {toolchain_type}: {str(e)}") + + def save_config(self, output_path: str) -> None: + formatted_toolchains = {} + for toolchain_type, tools in self.toolchains.items(): + formatted_toolchains[toolchain_type] = [ + {"name": tool.name, "path": tool.path} for tool in tools + ] + + try: + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(formatted_toolchains, f, indent=4, ensure_ascii=False) + logger.info(f"Configuration saved to {output_path}") + except Exception as e: + logger.error(f"Error saving configuration: {str(e)}") + +def parse_args(): + # Get the directory where main.py is located + current_dir = os.path.dirname(os.path.abspath(__file__)) + default_config = os.path.join(current_dir, 'toolchain_config.json') + default_output = os.path.join(current_dir, 'toolchains.json') + + parser = argparse.ArgumentParser(description='Scan system for development toolchains') + parser.add_argument( + '--output', '-o', + type=str, + default=default_output, + help='Path to save the toolchains configuration (default: toolchains.json in current directory)' + ) + parser.add_argument( + '--config', '-c', + type=str, + default=default_config, + help='Path to toolchain configuration file (default: toolchain_config.json in current directory)' + ) + return parser.parse_args() + +def main(): + args = parse_args() + scanner = ToolchainScanner(args.config) + scanner.scan_system() + scanner.save_config(args.output) + +if __name__ == "__main__": + main() + diff --git a/src/scripts/toolchain/parsers/gcc_toolchain.py b/src/scripts/toolchain/parsers/gcc_toolchain.py new file mode 100644 index 000000000..ad840c857 --- /dev/null +++ b/src/scripts/toolchain/parsers/gcc_toolchain.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List, Dict +import os +import glob +from base_toolchain import BaseToolchain, ToolchainInfo + +class GccToolchain(BaseToolchain): + def __init__(self, patterns: Dict[str, str]): + super().__init__() + self.patterns = patterns + + def find_compiler_candidates(self, executables: List[str], compiler_name: str) -> List[str]: + compiler_paths = [] + cl = len(compiler_name) + + for executable in executables: + file_name = os.path.basename(executable) + if file_name == compiler_name: + compiler_paths.append(executable) + continue + if file_name in ["c89-gcc", "c99-gcc"]: + continue + pos = file_name.find(compiler_name) + if pos == -1: + continue + if pos > 0 and file_name[pos - 1] != '-': + continue + + pos += cl + if pos != len(file_name): + if pos + 1 >= len(file_name): + continue + c = file_name[pos + 1] + if not c.isdigit(): + continue + compiler_paths.append(executable) + + return compiler_paths + + def scan(self) -> List[ToolchainInfo]: + executables = [] + paths = os.environ.get('PATH', '').split(os.pathsep) + priority_paths = ['/usr/bin', '/bin'] + other_paths = [p for p in paths if p not in priority_paths] + scan_paths = priority_paths + other_paths + + for path in scan_paths: + if not os.path.exists(path): + continue + for pattern in self.patterns.values(): + matches = glob.glob(os.path.join(path, pattern)) + for match in matches: + if os.access(match, os.X_OK | os.F_OK): + executables.append(match) + + toolchains = [] + for compiler_type in self.patterns.keys(): + candidates = self.find_compiler_candidates(executables, compiler_type) + detected = self.auto_detect_toolchains(candidates) + toolchains.extend(detected) + + return [ + ToolchainInfo( + name=os.path.basename(path), + path=path + ) for path in toolchains + ] \ No newline at end of file diff --git a/src/scripts/toolchain/parsers/java_toolchain.py b/src/scripts/toolchain/parsers/java_toolchain.py new file mode 100644 index 000000000..767777fbc --- /dev/null +++ b/src/scripts/toolchain/parsers/java_toolchain.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List +import os +import subprocess +from base_toolchain import BaseToolchain, ToolchainInfo + +class JavaToolchain(BaseToolchain): + def __init__(self): + super().__init__() + self.tool_name = "java" + + def get_java_version(self, java_path: str) -> str: + try: + process = subprocess.Popen( + [java_path, "-version"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + _, stderr = process.communicate() + + first_line = stderr.split('\n')[0] + parts = first_line.split() + if len(parts) >= 3: + version = parts[2].replace('"', '') + return f"{parts[0]} {version}" + return os.path.basename(java_path) + except Exception as e: + return os.path.basename(java_path) + + def scan(self) -> List[ToolchainInfo]: + paths = self.find_tools(self.tool_name) + return [ + ToolchainInfo( + name=self.get_java_version(path), + path=path + ) for path in paths + ] \ No newline at end of file diff --git a/src/scripts/toolchain/parsers/python_toolchain.py b/src/scripts/toolchain/parsers/python_toolchain.py new file mode 100644 index 000000000..b4715d986 --- /dev/null +++ b/src/scripts/toolchain/parsers/python_toolchain.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List +import os +import glob +from base_toolchain import BaseToolchain, ToolchainInfo + +class PythonToolchain(BaseToolchain): + def __init__(self): + super().__init__() + self.filter_patterns = [ + "python", + "python[1-9]", + "python[1-9].[0-9]", + "python[1-9].[1-9][0-9]" + ] + + def scan(self) -> List[ToolchainInfo]: + interpreters = [] + paths = os.environ.get("PATH", "").split(os.pathsep) + + for path in paths: + if not os.path.exists(path): + continue + for pattern in self.filter_patterns: + matches = glob.glob(os.path.join(path, pattern)) + for match in matches: + if os.access(match, os.X_OK | os.F_OK): + interpreters.append(os.path.realpath(match)) + + detected_paths = self.auto_detect_toolchains(interpreters) + return [ + ToolchainInfo( + name=os.path.basename(path), + path=path + ) for path in detected_paths + ] \ No newline at end of file diff --git a/src/scripts/toolchain/parsers/simple_toolchain.py b/src/scripts/toolchain/parsers/simple_toolchain.py new file mode 100644 index 000000000..c8f38fb66 --- /dev/null +++ b/src/scripts/toolchain/parsers/simple_toolchain.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import List +import os +from base_toolchain import BaseToolchain, ToolchainInfo + +class SimpleToolchain(BaseToolchain): + def __init__(self, tool_name: str): + super().__init__() + self.tool_name = tool_name + + def scan(self) -> List[ToolchainInfo]: + paths = self.find_tools(self.tool_name) + return [ + ToolchainInfo( + name=os.path.basename(path), + path=path + ) for path in paths + ] \ No newline at end of file diff --git a/src/scripts/toolchain/toolchain_config.json b/src/scripts/toolchain/toolchain_config.json new file mode 100644 index 000000000..59d8cde58 --- /dev/null +++ b/src/scripts/toolchain/toolchain_config.json @@ -0,0 +1,63 @@ +{ + "toolchains": [ + { + "type": "C compilers", + "parser": "GccToolchain", + "tools": { + "gcc": "*gcc*", + "clang": "*clang*" + } + }, + { + "type": "C++ compilers", + "parser": "GccToolchain", + "tools": { + "g++": "*g++*", + "clang++": "*clang++*" + } + }, + { + "type": "Python", + "parser": "PythonToolchain" + }, + { + "type": "C/C++ build systems", + "parser": "SimpleToolchain", + "tools": { + "cmake": "cmake" + } + }, + { + "type": "C/C++ debuggers", + "parser": "SimpleToolchain", + "tools": { + "gdb": "gdb" + } + }, + { + "type": "JDK", + "parser": "JavaToolchain" + }, + { + "type": "Maven", + "parser": "SimpleToolchain", + "tools": { + "mvn": "mvn" + } + }, + { + "type": "Gradle", + "parser": "SimpleToolchain", + "tools": { + "gradle": "gradle" + } + }, + { + "type": "Ninja", + "parser": "SimpleToolchain", + "tools": { + "ninja": "ninja" + } + } + ] +} \ No newline at end of file diff --git a/src/scripts/toolchain/toolchain_factory.py b/src/scripts/toolchain/toolchain_factory.py new file mode 100644 index 000000000..d846ec4af --- /dev/null +++ b/src/scripts/toolchain/toolchain_factory.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import json +import os +import importlib.util +import inspect +from typing import Dict, Type, Callable, List, Tuple +from base_toolchain import BaseToolchain + +class ToolchainFactory: + def __init__(self, config_file: str = "toolchain_config.json"): + self._creators: Dict[str, Callable[[], BaseToolchain]] = {} + self._parsers = self._load_parsers() + self._load_config(config_file) + + def _load_parsers(self) -> Dict[str, Type[BaseToolchain]]: + """Dynamically load all parser classes from the parsers directory""" + parsers = {} + parsers_dir = os.path.join(os.path.dirname(__file__), 'parsers') + + # Iterate through all .py files in parsers directory + for filename in os.listdir(parsers_dir): + if filename.endswith('.py'): + module_name = filename[:-3] # Remove .py extension + module_path = os.path.join(parsers_dir, filename) + + # Use importlib.util to dynamically import module + spec = importlib.util.spec_from_file_location(module_name, module_path) + if spec and spec.loader: + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Find all classes in the module + for name, obj in inspect.getmembers(module, inspect.isclass): + # Check if it's a subclass of BaseToolchain but not BaseToolchain itself + if (issubclass(obj, BaseToolchain) and + obj != BaseToolchain and + obj.__module__ == module_name): + parsers[name] = obj + + return parsers + + def _load_config(self, config_file: str): + with open(config_file, 'r') as f: + config = json.load(f) + + for toolchain in config['toolchains']: + toolchain_type = toolchain['type'] + parser_class = self._parsers[toolchain['parser']] + + if 'tools' in toolchain: + if parser_class.__name__ == 'SimpleToolchain': + # For SimpleToolchain, only pass the tool name + tool_name = next(iter(toolchain['tools'].keys())) + self._creators[toolchain_type] = lambda n=tool_name: self._parsers['SimpleToolchain'](n) + else: + # For other parsers that need complete tool configuration + self._creators[toolchain_type] = lambda t=toolchain['tools'], p=parser_class: p(t) + else: + # No tools configuration, use parser class directly + self._creators[toolchain_type] = parser_class + + def get_supported_types(self) -> List[str]: + """Get all supported toolchain types""" + return list(self._creators.keys()) + + def create_toolchain(self, toolchain_type: str) -> BaseToolchain: + creator = self._creators.get(toolchain_type) + if not creator: + raise ValueError(f"Unknown toolchain type: {toolchain_type}") + return creator() \ No newline at end of file diff --git a/src/services/option/toolchaindata.cpp b/src/services/option/toolchaindata.cpp index 1d6557c7e..25c310b01 100644 --- a/src/services/option/toolchaindata.cpp +++ b/src/services/option/toolchaindata.cpp @@ -34,7 +34,7 @@ bool ToolChainData::readToolChain(QString &filePath) return false; } - auto parseSubObj = [this](QJsonObject &obj, const QString &subobjName) { + auto parseSubObj = [this](const QJsonObject &obj, const QString &subobjName) { if (obj.contains(subobjName)) { QJsonValue cCompilersArray = obj.value(subobjName); QJsonArray array = cCompilersArray.toArray(); @@ -61,12 +61,9 @@ bool ToolChainData::readToolChain(QString &filePath) } }; - QJsonArray array = doc.array(); - for (auto node : array) { - auto obj = node.toObject(); - for (auto it = obj.begin(); it != obj.end(); it++) { - parseSubObj(obj, it.key()); - } + const auto &tcObj = doc.object(); + for (auto it = tcObj.begin(); it != tcObj.end(); it++) { + parseSubObj(tcObj, it.key()); } return true;