diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55c2b15..0c7753f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,12 +10,14 @@ permissions: jobs: test: - runs-on: windows-latest strategy: fail-fast: false matrix: + os: [windows-latest, macos-latest] python-version: ["3.11", "3.12", "3.13"] + runs-on: ${{ matrix.os }} + steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index d96be98..a9b5a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Added +- macOS 支持:按平台解析 DevEco JBR / hap-sign-tool / hdc 路径,可用 `hapsign` 命令行签名安装。 - 可安装的 `hapsign` 命令和标准 Python 项目元数据。 - Ruff、pytest、覆盖率、pre-commit 和 Windows CI 配置。 - 贡献指南、安全策略、行为准则和统一编辑器配置。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03d1da9..828c89e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,21 +5,21 @@ Profile、设备 UDID 或签名后的 HAP 加入仓库。 ## 开发环境 -项目要求 Windows 和 Python 3.11 或更高版本。创建并激活虚拟环境后安装开发依赖: +项目要求 Windows 或 macOS,以及 Python 3.11 或更高版本。创建并激活虚拟环境后安装开发依赖: -```powershell +```bash python -m pip install -r requirements-dev.txt ``` 需要手动验证完整登录流程时,再安装 Playwright 浏览器: -```powershell +```bash playwright install chromium ``` ## 提交前检查 -```powershell +```bash python -m ruff format . python -m ruff check . python -m pytest --cov diff --git a/README.md b/README.md index 77bb382..0cd8808 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,13 @@ Playwright 打开华为登录页(用户手动登录) ## 前置条件 -1. **DevEco Studio** 已安装(需要其中的 JBR Java、hap-sign-tool.jar、hdc.exe) +1. **DevEco Studio** 已安装(需要其中的 JBR Java、hap-sign-tool.jar、hdc) 2. **Python 3.11+**(推荐使用 conda 或 venv 隔离环境) 3. **HarmonyOS 设备**已通过 USB 连接并开启 USB 调试模式 4. **华为开发者账号**(需要已完成实名认证) +支持 **Windows** 与 **macOS**(命令行)。 + ## 安装 ```bash @@ -39,23 +41,31 @@ playwright install chromium ### 配置 DevEco Studio 路径 -默认查找路径为 `D:\Program Files\Huawei\DevEco Studio`。如果你的 DevEco Studio 安装在其他位置,设置环境变量: +默认查找路径: -```bat -:: Windows CMD +- Windows: `D:\Program Files\Huawei\DevEco Studio` +- macOS: `/Applications/DevEco-Studio.app/Contents` + +若安装在其他位置,设置环境变量: + +```bash +# macOS / Linux shell +export DEVECO_HOME="/Applications/DevEco-Studio.app/Contents" + +# Windows CMD set DEVECO_HOME=E:\DevEco Studio -:: PowerShell +# Windows PowerShell $env:DEVECO_HOME = "E:\DevEco Studio" ``` 调试密钥库默认使用兼容 DevEco 调试流程的固定密码。如需覆盖,请设置: -```powershell -$env:HAPSIGN_KEYSTORE_PASSWORD = "使用你自己的强密码" +```bash +export HAPSIGN_KEYSTORE_PASSWORD="使用你自己的强密码" ``` -### 配置 Python 路径(bat 脚本用) +### 配置 Python 路径(Windows bat 脚本用) `sign_install.bat` 默认使用系统 PATH 中的 `python`。如果使用 conda/venv,设置环境变量: @@ -69,19 +79,19 @@ $env:HAPSIGN_PYTHON = "C:\path\to\your\python.exe" ## 使用 -### 方式一:拖拽(推荐) - -将 `.hap` 文件直接拖到 `sign_install.bat` 上,自动完成签名+安装。 - -### 方式二:命令行 +### 方式一:命令行(Windows / macOS) ```bash -hapsign --hap path\to\app-unsigned.hap +hapsign --hap path/to/app-unsigned.hap ``` 包名会自动从 hap 内的 `module.json` 提取,无需手动指定。 源码目录中仍可使用 `python main.py --hap ...`。 +### 方式二:拖拽(仅 Windows) + +将 `.hap` 文件直接拖到 `sign_install.bat` 上,自动完成签名+安装。 + ### 完整参数 ``` @@ -152,7 +162,7 @@ signing_files/com.example.myapp/ ## 限制 - 登录验证码 / 二次验证需要用户在浏览器中手动处理 -- 仅支持 Windows(SDK 路径、bat 脚本均为 Windows 环境) +- 拖拽安装脚本仅支持 Windows(`sign_install.bat`);macOS 请使用 `hapsign` 命令行 - 签名流程依赖华为云 API,需要有网络连接和华为开发者账号 ## 开发与贡献 diff --git a/hapsign/config.py b/hapsign/config.py index 5ece82e..6fa5ace 100644 --- a/hapsign/config.py +++ b/hapsign/config.py @@ -2,6 +2,7 @@ 所有逆向获得的真实值集中在此,便于维护。""" import os +import sys # ── 域名 ────────────────────────────────────────────────────── # 登录/认证域名(从 IdeSystem.properties 逆向获得) @@ -73,29 +74,53 @@ ERR_APP_ID_INVALID = 205389959 ERR_TOKEN_INVALID_CODE = 4000 # 响应 code=4000 表示 token 失效 + # ── 本机 SDK 路径(可通过环境变量覆盖)────────────────────── -_DEVECO_HOME = os.environ.get( - "DEVECO_HOME", - r"D:\Program Files\Huawei\DevEco Studio", -) -DEVECO_JBR = os.path.join(_DEVECO_HOME, "jbr", "bin", "java.exe") -HAP_SIGN_TOOL = os.path.join( - _DEVECO_HOME, - "sdk", - "default", - "openharmony", - "toolchains", - "lib", - "hap-sign-tool.jar", -) -HDC_PATH = os.path.join( - _DEVECO_HOME, - "sdk", - "default", - "openharmony", - "toolchains", - "hdc.exe", -) +def default_deveco_home(platform: str | None = None) -> str: + """返回当前平台的 DevEco Studio 默认安装根目录。""" + plat = sys.platform if platform is None else platform + if plat == "darwin": + return "/Applications/DevEco-Studio.app/Contents" + if plat == "win32": + return r"D:\Program Files\Huawei\DevEco Studio" + return "/opt/DevEco-Studio" + + +def resolve_sdk_paths( + deveco_home: str | None = None, + platform: str | None = None, +) -> tuple[str, str, str, str]: + """解析 java / hap-sign-tool / hdc / keytool 路径。 + + Returns: + (java, hap_sign_tool, hdc, keytool) + """ + plat = sys.platform if platform is None else platform + home = deveco_home if deveco_home is not None else default_deveco_home(plat) + toolchains = os.path.join(home, "sdk", "default", "openharmony", "toolchains") + hap_sign_tool = os.path.join(toolchains, "lib", "hap-sign-tool.jar") + + if plat == "darwin": + bin_dir = os.path.join(home, "jbr", "Contents", "Home", "bin") + java = os.path.join(bin_dir, "java") + keytool = os.path.join(bin_dir, "keytool") + hdc = os.path.join(toolchains, "hdc") + elif plat == "win32": + bin_dir = os.path.join(home, "jbr", "bin") + java = os.path.join(bin_dir, "java.exe") + keytool = os.path.join(bin_dir, "keytool.exe") + hdc = os.path.join(toolchains, "hdc.exe") + else: + bin_dir = os.path.join(home, "jbr", "bin") + java = os.path.join(bin_dir, "java") + keytool = os.path.join(bin_dir, "keytool") + hdc = os.path.join(toolchains, "hdc") + + return java, hap_sign_tool, hdc, keytool + + +_DEVECO_HOME = os.environ.get("DEVECO_HOME") or default_deveco_home() +DEVECO_JBR, HAP_SIGN_TOOL, HDC_PATH, KEYTOOL_PATH = resolve_sdk_paths(_DEVECO_HOME) # ── HTTP header 常量 ────────────────────────────────────────── HEADER_OAUTH2_TOKEN = "oauth2Token" diff --git a/hapsign/signing/keytool_util.py b/hapsign/signing/keytool_util.py index 2583681..1498768 100644 --- a/hapsign/signing/keytool_util.py +++ b/hapsign/signing/keytool_util.py @@ -11,9 +11,8 @@ class KeytoolUtil: @staticmethod def _get_keytool_path() -> str: - """从 DEVECO_JBR 路径推导 keytool.exe 路径。""" - jbr_dir = os.path.dirname(config.DEVECO_JBR) - return os.path.join(jbr_dir, "keytool.exe") + """返回与当前平台匹配的 keytool 路径。""" + return config.KEYTOOL_PATH def generate_keypair( self, diff --git a/pyproject.toml b/pyproject.toml index a5c9b25..a42e949 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", diff --git a/tests/test_config_paths.py b/tests/test_config_paths.py new file mode 100644 index 0000000..5adc5e6 --- /dev/null +++ b/tests/test_config_paths.py @@ -0,0 +1,34 @@ +"""SDK 路径解析测试。""" + +import os + +from hapsign.config import default_deveco_home, resolve_sdk_paths + + +def test_default_deveco_home_by_platform() -> None: + assert default_deveco_home("darwin") == "/Applications/DevEco-Studio.app/Contents" + assert default_deveco_home("win32") == r"D:\Program Files\Huawei\DevEco Studio" + assert default_deveco_home("linux") == "/opt/DevEco-Studio" + + +def test_resolve_sdk_paths_darwin() -> None: + home = "/Applications/DevEco-Studio.app/Contents" + java, hap_sign, hdc, keytool = resolve_sdk_paths(home, "darwin") + + assert java == os.path.join(home, "jbr", "Contents", "Home", "bin", "java") + assert keytool == os.path.join(home, "jbr", "Contents", "Home", "bin", "keytool") + assert hdc.endswith(os.path.join("toolchains", "hdc")) + assert hap_sign.endswith(os.path.join("lib", "hap-sign-tool.jar")) + assert not java.endswith(".exe") + assert not hdc.endswith(".exe") + + +def test_resolve_sdk_paths_win32() -> None: + home = r"D:\Program Files\Huawei\DevEco Studio" + java, hap_sign, hdc, keytool = resolve_sdk_paths(home, "win32") + + assert java.endswith("java.exe") + assert keytool.endswith("keytool.exe") + assert hdc.endswith("hdc.exe") + assert hap_sign.endswith("hap-sign-tool.jar") + assert os.path.join("jbr", "bin") in java.replace("/", "\\") or "jbr" in java