From 01305c8a1602b776fbabb5a1e8e354ffd7c63b68 Mon Sep 17 00:00:00 2001 From: Dongly Date: Sat, 12 Sep 2026 17:56:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=8F=82=E6=95=B0=E3=80=81info=20=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E5=B1=82=E4=B8=8E=E4=BB=93=E5=BA=93=20URL=20=E9=9B=86?= =?UTF-8?q?=E4=B8=AD=E5=8C=96=20(Add=20--info=20flag,=20info=20access=20la?= =?UTF-8?q?yer=20and=20centralized=20repository=20URLs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 21 +-- .gitignore | 11 ++ cmds/cmd_package/cmd_package_update.py | 6 +- cmds/cmd_package/cmd_package_upgrade.py | 38 ++---- cmds/cmd_package/cmd_package_utils.py | 4 +- env.json | 35 ++++- env.ps1 | 5 + env.py | 62 ++++++--- env.sh | 5 + env_venv.py | 4 + info.py | 171 ++++++++++++++++++++++++ plugins/compatibility.py | 6 +- plugins/tests/test_info.py | 121 +++++++++++++++++ pyproject.toml | 54 ++++++++ setup.py | 75 ++--------- statistics.py | 5 +- version.py | 64 --------- 17 files changed, 497 insertions(+), 190 deletions(-) create mode 100644 info.py create mode 100644 plugins/tests/test_info.py delete mode 100644 version.py diff --git a/.gitattributes b/.gitattributes index 13da7fbe..918d4592 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,17 +3,17 @@ # Handle line endings automatically for files detected as text # and leave all files detected as binary untouched. # ============ -* text=auto +* text=auto eol=lf # Source files # ============ -*.pxd text eol=crlf diff=python -*.py text eol=crlf diff=python -*.py3 text eol=crlf diff=python -*.pyw text eol=crlf diff=python -*.pyx text eol=crlf diff=python -*.pyz text eol=crlf diff=python -*.pyi text eol=crlf diff=python +*.pxd text diff=python +*.py text diff=python +*.py3 text diff=python +*.pyw text diff=python +*.pyx text diff=python +*.pyz text diff=python +*.pyi text diff=python # Binary files # ============ @@ -26,7 +26,8 @@ *.pyo binary # MarkDown -*.md text eol=crlf +*.md text *.bat text eol=crlf -*.ps1 text eol=crlf \ No newline at end of file +*.cmd text eol=crlf +*.ps1 text eol=crlf diff --git a/.gitignore b/.gitignore index eaa1cb4e..dfab293e 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,14 @@ plugins/webui/frontend/test-results/ docs documentation + +# Temporary files +tmp +temp +*.bak + +# AI +.omo/ +.opencode/ +AGENTS.md +CONTEXT.md diff --git a/cmds/cmd_package/cmd_package_update.py b/cmds/cmd_package/cmd_package_update.py index 7fb712f6..f0c4b0c8 100644 --- a/cmds/cmd_package/cmd_package_update.py +++ b/cmds/cmd_package/cmd_package_update.py @@ -23,6 +23,7 @@ # 2020-04-08 SummerGift Optimize program structure # 2020-04-13 SummerGift refactoring # 2026-05-12 CYFS share hal-sdk packages in libraries and create bridge SConscript for hal-sdk packages in BSP packages +# 2026-09-12 Dongly Resolve submodule mirror urls via info.get_submodule_mirror_url # import json @@ -43,6 +44,7 @@ import pkgsdb from package import PackageOperation, Bridge_SConscript from vars import Import, Export +from info import get_submodule_mirror_url from .cmd_package_utils import ( get_url_from_mirror_server, execute_command, @@ -319,7 +321,7 @@ def get_mirror_giturl(submodule_name): Retrurn the download address of the submodule on the mirror server from the submod_name. """ - mirror_url = 'https://gitee.com/RT-Thread-Mirror/submod_' + submodule_name + '.git' + mirror_url = get_submodule_mirror_url('rt-thread', submodule_name) return mirror_url @@ -328,7 +330,7 @@ def get_esp_mirror_giturl(submodule_name): submodule_name = "CException" elif submodule_name == "unity": submodule_name = "Unity" - mirror_url = 'https://gitee.com/esp-submodules/' + submodule_name + '.git' + mirror_url = get_submodule_mirror_url('esp', submodule_name) return mirror_url diff --git a/cmds/cmd_package/cmd_package_upgrade.py b/cmds/cmd_package/cmd_package_upgrade.py index ce245145..9745ca04 100644 --- a/cmds/cmd_package/cmd_package_upgrade.py +++ b/cmds/cmd_package/cmd_package_upgrade.py @@ -21,12 +21,16 @@ # Change Logs: # Date Author Notes # 2020-04-08 SummerGift Optimize program structure +# 2026-09-12 Dongly Resolve packages/env repo URLs and statistics endpoint +# via env.json (info.get_source / info.get_api_url); logical +# repos no longer query the mirror server # import os import uuid from vars import Import -from .cmd_package_utils import execute_command, git_pull_repo, get_url_from_mirror_server, find_bool_macro_in_config +from info import get_source, get_api_url +from .cmd_package_utils import execute_command, git_pull_repo, find_bool_macro_in_config from .cmd_package_update import need_using_mirror_download try: @@ -49,16 +53,8 @@ def upgrade_packages_index(force_upgrade=False): pkgs_root = Import('pkgs_root') - if need_using_mirror_download(): - get_package_url, get_ver_sha = get_url_from_mirror_server('packages', 'latest') - - if get_package_url is not None: - git_repo = get_package_url - else: - print("Failed to get url from mirror server. Using default url.") - git_repo = 'https://gitee.com/RT-Thread-Mirror/packages.git' - else: - git_repo = 'https://github.com/RT-Thread/packages.git' + src = get_source('packages', use_mirror=need_using_mirror_download()) + git_repo = src.url packages_root = pkgs_root pkgs_path = os.path.join(packages_root, 'packages') @@ -70,7 +66,7 @@ def upgrade_packages_index(force_upgrade=False): else: if force_upgrade: execute_command('git fetch --all', cwd=pkgs_path) - execute_command('git reset --hard origin/master', cwd=pkgs_path) + execute_command('git reset --hard origin/%s' % src.branch, cwd=pkgs_path) print("Begin to upgrade env packages.") git_pull_repo(pkgs_path, git_repo) print("==============================> Env packages upgrade done \n") @@ -96,23 +92,14 @@ def upgrade_env_script(force_upgrade=False): env_root = Import('env_root') - if need_using_mirror_download(): - get_package_url, get_ver_sha = get_url_from_mirror_server('env', 'latest') - - if get_package_url is not None: - env_scripts_repo = get_package_url - else: - print("Failed to get url from mirror server. Using default url.") - env_scripts_repo = 'https://gitee.com/RT-Thread-Mirror/env.git' - else: - env_scripts_repo = 'https://github.com/RT-Thread/env.git' + src = get_source('env', use_mirror=need_using_mirror_download()) env_scripts_root = os.path.join(env_root, 'tools', 'scripts') if force_upgrade: execute_command('git fetch --all', cwd=env_scripts_root) - execute_command('git reset --hard origin/master', cwd=env_scripts_root) + execute_command('git reset --hard origin/%s' % src.branch, cwd=env_scripts_root) print("Begin to upgrade env scripts.") - git_pull_repo(env_scripts_root, env_scripts_repo) + git_pull_repo(env_scripts_root, src.url) print("==============================> Env scripts upgrade done \n") @@ -130,7 +117,8 @@ def Information_statistics(): if os.path.isfile(env_config_file) and find_bool_macro_in_config(env_config_file, 'SYS_PKGS_USING_STATISTICS'): mac_addr = get_mac_address() response = requests.get( - 'https://www.rt-thread.org/studio/statistics/api/envuse?userid=' + get_api_url('statistics') + + '?userid=' + str(mac_addr) + '&username=' + str(mac_addr) diff --git a/cmds/cmd_package/cmd_package_utils.py b/cmds/cmd_package/cmd_package_utils.py index 022468a7..efc17ccb 100644 --- a/cmds/cmd_package/cmd_package_utils.py +++ b/cmds/cmd_package/cmd_package_utils.py @@ -21,6 +21,7 @@ # Change Logs: # Date Author Notes # 2020-04-08 SummerGift Optimize program structure +# 2026-09-12 Dongly Resolve mirror query endpoint via info.get_api_url # import json @@ -34,6 +35,7 @@ import requests import logging from vars import Import +from info import get_api_url def get_git_root_path(repo_path): @@ -220,7 +222,7 @@ def get_url_from_mirror_server(package_name, package_version): payload["packages"][0]['name'] = package_name try: - r = requests.post("https://api.rt-thread.org/packages/queries", data=json.dumps(payload)) + r = requests.post(get_api_url('mirror_query'), data=json.dumps(payload)) if r.status_code == requests.codes.ok: package_info = json.loads(r.text) diff --git a/env.json b/env.json index 674e979e..96f91b07 100644 --- a/env.json +++ b/env.json @@ -2,7 +2,38 @@ "name": "RT-Thread Env Tool", "version": "v2.0.2", "description": "A command-line toolkit for RT-Thread development.", - "repository": { - "url": "https://github.com/RT-Thread/env" + "repositories": { + "env": { + "url": "https://github.com/RT-Thread/env.git", + "branch": "master", + "mirror": { + "url": "https://gitee.com/RT-Thread-Mirror/env.git", + "branch": "master" + } + }, + "sdk": { + "url": "https://github.com/RT-Thread/sdk.git", + "branch": "main", + "mirror": { + "url": "https://gitee.com/RT-Thread-Mirror/sdk.git", + "branch": "main" + } + }, + "packages": { + "url": "https://github.com/RT-Thread/packages.git", + "branch": "master", + "mirror": { + "url": "https://gitee.com/RT-Thread-Mirror/packages.git", + "branch": "master" + } + } + }, + "submodule_mirrors": { + "rt-thread": "https://gitee.com/RT-Thread-Mirror/submod_{name}.git", + "esp": "https://gitee.com/esp-submodules/{name}.git" + }, + "apis": { + "mirror_query": "https://api.rt-thread.org/packages/queries", + "statistics": "https://www.rt-thread.org/studio/statistics/api/envuse" } } diff --git a/env.ps1 b/env.ps1 index 7f39f8c0..78e48de5 100644 --- a/env.ps1 +++ b/env.ps1 @@ -42,6 +42,11 @@ if (Test-Path -Path $ActivateScript -PathType Leaf) { Write-Error "Failed to activate the RT-Thread Env Python venv: $_" $ActivateStatus = 1 } + + # Show welcome message using rt-env command + if (Get-Command rt-env -ErrorAction SilentlyContinue) { + rt-env --info + } } else { Write-Error "Cannot activate the RT-Thread Env Python venv: $ActivateScript was not found." $ActivateStatus = 1 diff --git a/env.py b/env.py index 64367693..6481ea3e 100644 --- a/env.py +++ b/env.py @@ -24,6 +24,7 @@ # 2019-1-16 SummerGift Add chinese detection # 2020-4-13 SummerGift refactoring # 2025-1-27 bernard Add env.json for env information +# 2026-09-12 Dongly Add show_version banner with --info flag; migrate to info accessors import os import sys @@ -38,17 +39,31 @@ from cmds import * from vars import Export -from version import get_rt_env_version +from info import get_name, get_version -def show_version_warning(): +def show_version(): + rtt_ver = get_rtt_verion() + rt_env_name, rt_env_ver = get_name(), get_version() + + print('\033[1;36m===================================================================\033[0m') + print('\033[1;36m Welcome to %s %s\033[0m' % (rt_env_name, rt_env_ver)) + print('\033[1;36m===================================================================\033[0m') + print('Environment Information:') + print(' - ENV_ROOT : %s' % get_env_root()) + print(' - PKGS_ROOT: %s' % get_package_root()) + + if rtt_ver != (0, 0, 0): + print(' - RTT_ROOT : %s' % get_rtt_root()) + print(' - BSP_ROOT : %s' % get_bsp_root()) + print(' - RT-Thread Version: %d.%d.%d' % rtt_ver) + print('\033[1;36m===================================================================\033[0m') + +def show_version_warning(is_show_version=True): rtt_ver = get_rtt_verion() - rt_env_name, rt_env_ver = get_rt_env_version() if rtt_ver <= (5, 1, 0) and rtt_ver != (0, 0, 0): - print('===================================================================') - print('Welcome to %s %s' % (rt_env_name, rt_env_ver)) - print('===================================================================') - # print('') + if is_show_version: + show_version() print('env v2.0 has made the following important changes:') print('1. Upgrading Python version from v2 to v3') print('2. Replacing kconfig-frontends with Python kconfiglib') @@ -67,12 +82,14 @@ def show_version_warning(): def init_argparse(): - parser = argparse.ArgumentParser(description=__doc__) + # 'rt-env' mirrors the [project.scripts] entry in pyproject.toml + parser = argparse.ArgumentParser(prog='rt-env', description=__doc__) subs = parser.add_subparsers() - rt_env_name, rt_env_ver = get_rt_env_version() + rt_env_name, rt_env_ver = get_name(), get_version() env_ver_str = '%s %s' % (rt_env_name, rt_env_ver) parser.add_argument('-v', '--version', action='version', version=env_ver_str) + parser.add_argument('--info', action='store_true', help='Show environment information') cmd_system.add_parser(subs) cmd_menuconfig.add_parser(subs) @@ -219,18 +236,31 @@ def exec_arg(arg): args.func(args) -def main(): - show_version_warning() - export_environment_variable() - init_logger(get_env_root()) +def cmd_env_info(args): + """Handle environment information display.""" + show_version() + show_version_warning(False) + sys.exit(0) + +def main(): parser = init_argparse() args = parser.parse_args() - if not vars(args): + if args.info: + cmd_env_info(args) + + # Check if any subcommand was provided + if not hasattr(args, 'func'): + # No subcommand provided, show help parser.print_help() - else: - args.func(args) + sys.exit(0) + + show_version_warning() + export_environment_variable() + init_logger(get_env_root()) + + args.func(args) def menuconfig(): diff --git a/env.sh b/env.sh index 044c8936..ff2c3926 100644 --- a/env.sh +++ b/env.sh @@ -42,6 +42,11 @@ if [ -f "$ENV_ACTIVATE" ]; then echo "Failed to activate the RT-Thread Env Python venv." >&2 activate_status=1 fi + + # Show welcome message using rt-env command + if command -v rt-env >/dev/null 2>&1; then + rt-env --info + fi else echo "Cannot activate the RT-Thread Env Python venv: $ENV_ACTIVATE was not found." >&2 activate_status=1 diff --git a/env_venv.py b/env_venv.py index 33460e13..db576141 100644 --- a/env_venv.py +++ b/env_venv.py @@ -123,6 +123,10 @@ def source_fingerprint(source_root): return digest.hexdigest() +# NOTE: deliberately NOT using info.load_env_json() — this reads the SOURCE +# tree being installed (the source_root parameter), not the running copy, and +# env_venv must stay self-contained so degraded installs can still bootstrap. +# Do not "fix" this duplication without considering the reasons above. def read_env_version(source_root): path = _normalized(source_root) / 'env.json' try: diff --git a/info.py b/info.py new file mode 100644 index 00000000..6d6d1f4b --- /dev/null +++ b/info.py @@ -0,0 +1,171 @@ +# -*- coding:utf-8 -*- +# +# File : info.py +# This file is part of RT-Thread RTOS +# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Change Logs: +# Date Author Notes +# 2025-06-23 Dongly Add get_rt_env_version function +# 2026-09-10 Dongly Add get_rt_env_description function, Extract load_env_json common helper +# 2026-09-12 Dongly Rename version.py to info.py; single access layer for env.json (metadata, repositories, submodule mirrors, service endpoints) + +import json +import os +import platform + +from collections import namedtuple + +# Fallback snapshot of the shipped env.json, WITHOUT mirror entries: the +# defaults only guarantee the primary source of each repository. A mirror is +# an optional accelerator that comes from env.json alone — with no mirror +# configured, the primary source is used. +DEFAULTS = { + 'name': 'RT-Thread Env Tool', + 'version': 'v2.0.2', + 'description': 'A command-line toolkit for RT-Thread development.', + 'repositories': { + 'env': { + 'url': 'https://github.com/RT-Thread/env.git', + 'branch': 'master', + }, + 'sdk': { + 'url': 'https://github.com/RT-Thread/sdk.git', + 'branch': 'main', + }, + 'packages': { + 'url': 'https://github.com/RT-Thread/packages.git', + 'branch': 'master', + }, + }, + 'submodule_mirrors': { + 'rt-thread': 'https://gitee.com/RT-Thread-Mirror/submod_{name}.git', + 'esp': 'https://gitee.com/esp-submodules/{name}.git', + }, + 'apis': { + 'mirror_query': 'https://api.rt-thread.org/packages/queries', + 'statistics': 'https://www.rt-thread.org/studio/statistics/api/envuse', + }, +} + +# A repository source is an atomic (url, branch) pair: selecting a mirror +# switches the whole pair, so a primary URL can never be paired with a +# mirror branch. +Source = namedtuple('Source', ['url', 'branch']) + + +def load_env_json(): + # try to read env.json to get information + try: + # Get the directory where this script is located + script_dir = os.path.dirname(os.path.abspath(__file__)) + env_json_path = os.path.join(script_dir, 'env.json') + + # If not found in script directory, try ENV_ROOT + if not os.path.exists(env_json_path): + env_root = os.getenv("ENV_ROOT") + if env_root is None: + if platform.system() != 'Windows': + env_root = os.path.join(os.getenv('HOME'), '.env') + else: + env_root = os.path.join(os.getenv('USERPROFILE'), '.env') + env_json_path = os.path.join(env_root, 'tools', 'scripts', 'env.json') + + with open(env_json_path, 'r') as file: + return json.load(file) + except Exception as e: + # Only print error if running interactively (not imported) + if __name__ == '__main__': + print("Failed to read env.json: %s" % str(e)) + + return None + + +def _config_section(section): + # return the raw config section ({} when absent or malformed) + config = load_env_json() or {} + value = config.get(section) + return value if isinstance(value, dict) else {} + + +def get_name(): + name = (load_env_json() or {}).get('name') + if not isinstance(name, str): + name = DEFAULTS['name'] + return name + + +def get_version(): + version = (load_env_json() or {}).get('version') + if not isinstance(version, str): + version = DEFAULTS['version'] + return version + + +def get_description(): + description = (load_env_json() or {}).get('description') + if not isinstance(description, str): + description = DEFAULTS['description'] + return description + + +def get_source(repo, use_mirror=False, branch=None): + # Resolve a repository source as Source(url, branch). + # Priority: explicit branch argument > configured branch of that source + # (a mirror without 'branch' inherits the primary branch) > DEFAULTS + # branch. DEFAULTS never supplies a mirror: with no mirror url in + # env.json, the primary source is used. Unknown repository names and + # entries without a url raise KeyError — config problems fail loudly. + raw_entry = _config_section('repositories').get(repo) + if not isinstance(raw_entry, dict): + raw_entry = {} + if repo not in DEFAULTS['repositories'] and not raw_entry: + raise KeyError('unknown repository: %r' % (repo,)) + + default_entry = DEFAULTS['repositories'].get(repo, {}) + url = raw_entry.get('url') or default_entry.get('url') + resolved_branch = raw_entry.get('branch') or default_entry.get('branch') + + if use_mirror: + mirror = raw_entry.get('mirror') if isinstance(raw_entry.get('mirror'), dict) else {} + if mirror.get('url'): + url = mirror['url'] + resolved_branch = mirror.get('branch') or resolved_branch + + if url is None: + raise KeyError("repository %r is configured without a 'url'" % (repo,)) + if branch is not None: + resolved_branch = branch + return Source(url=url, branch=resolved_branch) + + +def get_submodule_mirror_url(kind, name): + # Instantiate a submodule mirror template ('{name}' placeholder) for the + # given submodule name. kind: 'rt-thread' or 'esp'. ESP name case mapping + # (unity -> Unity) is caller-side logic, not configuration. + template = _config_section('submodule_mirrors').get(kind) or DEFAULTS['submodule_mirrors'].get(kind) + if not isinstance(template, str): + raise KeyError('unknown submodule mirror kind: %r' % (kind,)) + return template.replace('{name}', name) + + +def get_api_url(name): + # Service endpoints. name: 'mirror_query' or 'statistics'. + url = _config_section('apis').get(name) or DEFAULTS['apis'].get(name) + if not isinstance(url, str): + raise KeyError('unknown api name: %r' % (name,)) + return url diff --git a/plugins/compatibility.py b/plugins/compatibility.py index 3f8059fc..9d2ac4cd 100644 --- a/plugins/compatibility.py +++ b/plugins/compatibility.py @@ -101,10 +101,10 @@ def python_abi(): def current_env_version(): try: - from version import get_rt_env_version + from info import get_version except ImportError: - from env.version import get_rt_env_version - return get_rt_env_version()[1] + from env.info import get_version + return get_version() def compatibility_issues(manifest, env_version=None, system=None, architecture=None, implementation=None, abi=None): diff --git a/plugins/tests/test_info.py b/plugins/tests/test_info.py new file mode 100644 index 00000000..37e0cd8b --- /dev/null +++ b/plugins/tests/test_info.py @@ -0,0 +1,121 @@ +import json +import os +import unittest +from unittest import mock + +import info + + +def load_shipped_env_json(): + path = os.path.join(os.path.dirname(info.__file__), 'env.json') + with open(path, 'r') as source: + return json.load(source) + + +class DefaultsDriftTest(unittest.TestCase): + """Pin info.DEFAULTS to the shipped env.json. + + DEFAULTS intentionally has no mirror entries; every leaf it does define + must exist in the shipped env.json with the same value, otherwise the + fallback silently diverges from the real configuration. + """ + + def _assert_leaves_match(self, defaults, shipped, path): + for key, value in defaults.items(): + location = '.'.join(path + [str(key)]) + self.assertIn(key, shipped, 'shipped env.json is missing %s' % location) + if isinstance(value, dict): + self.assertIsInstance(shipped[key], dict, '%s should be an object' % location) + self._assert_leaves_match(value, shipped[key], path + [str(key)]) + else: + self.assertEqual(shipped[key], value, 'DEFAULTS drifted from env.json at %s' % location) + + def test_defaults_leaves_match_shipped_env_json(self): + self._assert_leaves_match(info.DEFAULTS, load_shipped_env_json(), []) + + +class InfoAccessorTest(unittest.TestCase): + def test_metadata_from_shipped_config(self): + config = load_shipped_env_json() + with mock.patch.object(info, 'load_env_json', return_value=config): + self.assertEqual(info.get_name(), config['name']) + self.assertEqual(info.get_version(), config['version']) + self.assertEqual(info.get_description(), config['description']) + + def test_metadata_falls_back_to_defaults_without_config(self): + with mock.patch.object(info, 'load_env_json', return_value=None): + self.assertEqual(info.get_name(), info.DEFAULTS['name']) + self.assertEqual(info.get_version(), info.DEFAULTS['version']) + self.assertEqual(info.get_description(), info.DEFAULTS['description']) + + def test_get_source_primary_and_mirror(self): + config = load_shipped_env_json() + with mock.patch.object(info, 'load_env_json', return_value=config): + primary = info.get_source('env') + mirror = info.get_source('env', use_mirror=True) + self.assertEqual(primary.url, config['repositories']['env']['url']) + self.assertEqual(primary.branch, config['repositories']['env']['branch']) + self.assertEqual(mirror.url, config['repositories']['env']['mirror']['url']) + self.assertEqual(mirror.branch, config['repositories']['env']['mirror']['branch']) + + def test_get_source_falls_back_to_defaults(self): + with mock.patch.object(info, 'load_env_json', return_value=None): + source = info.get_source('packages') + self.assertEqual(source.url, info.DEFAULTS['repositories']['packages']['url']) + self.assertEqual(source.branch, info.DEFAULTS['repositories']['packages']['branch']) + + def test_mirror_is_optional_and_falls_back_to_primary(self): + config = {'repositories': {'env': {'url': 'https://example.com/env.git', 'branch': 'dev'}}} + with mock.patch.object(info, 'load_env_json', return_value=config): + source = info.get_source('env', use_mirror=True) + self.assertEqual(source.url, 'https://example.com/env.git') + self.assertEqual(source.branch, 'dev') + + def test_mirror_branch_inherits_primary_branch(self): + config = {'repositories': {'env': {'url': 'u1', 'branch': 'v2', 'mirror': {'url': 'm1'}}}} + with mock.patch.object(info, 'load_env_json', return_value=config): + source = info.get_source('env', use_mirror=True) + self.assertEqual(source.url, 'm1') + self.assertEqual(source.branch, 'v2') + + def test_explicit_branch_overrides_configuration(self): + config = load_shipped_env_json() + with mock.patch.object(info, 'load_env_json', return_value=config): + source = info.get_source('env', branch='release-2.0') + self.assertEqual(source.branch, 'release-2.0') + + def test_unknown_repository_raises(self): + with mock.patch.object(info, 'load_env_json', return_value={}): + self.assertRaises(KeyError, info.get_source, 'no-such-repo') + + def test_unknown_api_raises(self): + with mock.patch.object(info, 'load_env_json', return_value={}): + self.assertRaises(KeyError, info.get_api_url, 'no-such-api') + + def test_custom_repo_from_config(self): + config = {'repositories': {'mine': {'url': 'git@example.com:me/env.git', 'branch': 'dev'}}} + with mock.patch.object(info, 'load_env_json', return_value=config): + source = info.get_source('mine') + self.assertEqual(source.url, 'git@example.com:me/env.git') + self.assertEqual(source.branch, 'dev') + + def test_custom_repo_without_url_raises(self): + config = {'repositories': {'mine': {'branch': 'dev'}}} + with mock.patch.object(info, 'load_env_json', return_value=config): + self.assertRaises(KeyError, info.get_source, 'mine') + + def test_submodule_mirror_template(self): + with mock.patch.object(info, 'load_env_json', return_value={}): + url = info.get_submodule_mirror_url('rt-thread', 'lwip') + self.assertEqual(url, 'https://gitee.com/RT-Thread-Mirror/submod_lwip.git') + + def test_api_url_from_config_or_defaults(self): + with mock.patch.object(info, 'load_env_json', return_value={}): + self.assertEqual(info.get_api_url('statistics'), info.DEFAULTS['apis']['statistics']) + config = {'apis': {'statistics': 'https://example.com/stats'}} + with mock.patch.object(info, 'load_env_json', return_value=config): + self.assertEqual(info.get_api_url('statistics'), 'https://example.com/stats') + + +if __name__ == '__main__': + unittest.main() diff --git a/pyproject.toml b/pyproject.toml index 3deafd54..43063ba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,57 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "rt-env" +dynamic = ["version", "description"] +requires-python = ">=3.7" +authors = [{ name = "RT-Thread Development Team", email = "rt-thread@rt-thread.org" }] +keywords = ["rt-thread"] +license = { text = "GPL-2.0-or-later" } +urls = { Homepage = "https://github.com/RT-Thread/env", Repository = "https://github.com/RT-Thread/env" } +dependencies = [ + "SCons>=4.0.0", + "requests", + "psutil", + "tqdm", + "kconfiglib", + "pyyaml", + "windows-curses; sys_platform=='win32'", +] + +[project.scripts] +# 'rt-env' doubles as the argparse prog in env.py init_argparse() +rt-env = "env.env:main" +menuconfig = "env.env:menuconfig" +pkgs = "env.env:pkgs" +sdk = "env.env:sdk" +system = "env.env:system" +webui = "env.env:webui" + +[tool.setuptools] +package-dir = { env = ".", "env.cmds" = "cmds", "env.cmds.cmd_package" = "cmds/cmd_package", "env.plugins" = "plugins", "env.plugins.sdk" = "plugins/sdk", "env.plugins.epack" = "plugins/epack", "env.plugins.spec" = "plugins/spec", "env.plugins.webui" = "plugins/webui" } +include-package-data = false +packages = [ + "env", + "env.cmds", + "env.cmds.cmd_package", + "env.plugins", + "env.plugins.sdk", + "env.plugins.epack", + "env.plugins.spec", + "env.plugins.webui", +] + +[tool.setuptools.package-data] +"env" = ["*.*"] +"env.plugins" = ["*.md", "bundled/**/*", "examples/**/*"] +"env.plugins.spec" = ["*.json", "*.md"] +"env.plugins.webui" = ["static/*", "static/assets/*"] + +[tool.setuptools.exclude-package-data] +"env" = ["MANIFEST.in"] + [tool.black] line-length = 128 skip-string-normalization = true diff --git a/setup.py b/setup.py index 0f0a667a..0950323c 100644 --- a/setup.py +++ b/setup.py @@ -1,70 +1,13 @@ from setuptools import setup -from version import get_rt_env_version +import sys +import os + +# Add current directory to path for the info module discovery +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from info import get_version, get_description -env_name, env_ver = get_rt_env_version() setup( - name='env', - version=env_ver, - description='A command-line toolkit for RT-Thread development.', - url='https://github.com/RT-Thread/env.git', - author='RT-Thread Development Team', - author_email='rt-thread@rt-thread.org', - keywords='rt-thread', - license='Apache License 2.0', - project_urls={ - 'Github repository': 'https:/github.com/rt-thread/env.git', - 'User guide': 'https:/github.com/rt-thread/env.git', - }, - python_requires='>=3.6', - install_requires=[ - 'SCons>=4.0.0', - 'requests', - 'psutil', - 'tqdm', - 'kconfiglib', - 'windows-curses; platform_system=="Windows"', - ], - packages=[ - 'env', - 'env.cmds', - 'env.cmds.cmd_package', - 'env.plugins', - 'env.plugins.sdk', - 'env.plugins.epack', - 'env.plugins.spec', - 'env.plugins.webui', - ], - package_dir={ - 'env': '.', - 'env.cmds': 'cmds', - 'env.cmds.cmd_package': 'cmds/cmd_package', - 'env.plugins': 'plugins', - 'env.plugins.sdk': 'plugins/sdk', - 'env.plugins.epack': 'plugins/epack', - 'env.plugins.spec': 'plugins/spec', - 'env.plugins.webui': 'plugins/webui', - }, - package_data={ - '': ['*.*'], - 'env.plugins.webui': ['static/*', 'static/assets/*'], - }, - exclude_package_data={ - '': ['MANIFEST.in'], - 'env.plugins.webui': [ - 'frontend/*', - 'frontend/e2e/*', - 'frontend/src/*', - ], - }, - include_package_data=True, - entry_points={ - 'console_scripts': [ - 'rt-env=env.env:main', - 'menuconfig=env.env:menuconfig', - 'pkgs=env.env:pkgs', - 'sdk=env.env:sdk', - 'system=env.env:system', - 'webui=env.env:webui', - ] - }, + version=get_version(), + description=get_description(), ) diff --git a/statistics.py b/statistics.py index ce156c98..d529510b 100644 --- a/statistics.py +++ b/statistics.py @@ -21,6 +21,7 @@ # Change Logs: # Date Author Notes # 2022-5-6 WuGenSheng Add copyright information +# 2026-09-12 Dongly Resolve statistics endpoint via info.get_api_url # import os import uuid @@ -28,6 +29,7 @@ import requests from vars import Import +from info import get_api_url from cmds import * @@ -48,7 +50,8 @@ def Information_statistics(): if not os.path.isfile(env_config_file): try: response = requests.get( - 'https://www.rt-thread.org/studio/statistics/api/envuse?userid=' + get_api_url('statistics') + + '?userid=' + str(mac_addr) + '&username=' + str(mac_addr) diff --git a/version.py b/version.py deleted file mode 100644 index 0bdd79c3..00000000 --- a/version.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding:utf-8 -*- -# -# File : version.py -# This file is part of RT-Thread RTOS -# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Change Logs: -# Date Author Notes -# 2025-06-23 Dongly Add get_rt_env_version function - -import json -import os -import platform - -def get_rt_env_version(): - rt_env_ver = None - rt_env_name = None - - # try to read env.json to get information - try: - # Get the directory where this script is located - script_dir = os.path.dirname(os.path.abspath(__file__)) - env_json_path = os.path.join(script_dir, 'env.json') - - # If not found in script directory, try ENV_ROOT - if not os.path.exists(env_json_path): - env_root = os.getenv("ENV_ROOT") - if env_root is None: - if platform.system() != 'Windows': - env_root = os.path.join(os.getenv('HOME'), '.env') - else: - env_root = os.path.join(os.getenv('USERPROFILE'), '.env') - env_json_path = os.path.join(env_root, 'tools', 'scripts', 'env.json') - - with open(env_json_path, 'r') as file: - env_data = json.load(file) - rt_env_name = env_data['name'] - rt_env_ver = env_data['version'] - except Exception as e: - # Only print error if running interactively (not imported) - if __name__ == '__main__': - print("Failed to read env.json: %s" % str(e)) - - if rt_env_name is None: - rt_env_name = 'RT-Thread Env Tool' - if rt_env_ver is None: - # use the default 'v2.0.1' - rt_env_ver = 'v2.0.1' - - return rt_env_name, rt_env_ver