pbnpm
v0.5.10
pbnpm —— PowerBuilder Nebula Package Manager,PowerBuilder 包管理器,为 PB 工程提供类 npm 的包安装、卸载、发布、模板创建与单元测试入口。可从 git 仓库拉取 PB 库(PBL/DLL/源码)安装到本地 .pbt 工程目录,或把本地包上传到 pbpkg 仓库,并自动维护 .pbt 的 LibList、pbnpm.lock 安装记录与 pbnpm.log 变更日志。
源码位置:pbnpm/src/
目录
- 快速安装
- 重点:在 PB9 工程中安装 pblit
- 编译与获取
- 运行前置条件
- 命令总览
- install — 安装包
- uninstall — 卸载包
- list — 列出已安装包
- upload — 上传包到仓库
- create — 从模板创建工程
- check — 检查重复对象名
- test — 运行 pypower 单元测试
- cache — 管理下载缓存
- 包清单(pbnpm.json)
- pbnpm.lock 安装记录
- pbnpm.log 日志
- 缓存机制
- EXE 内嵌 ob.exe 模式
- 许可证管理
- PBL 源码提取
- 自定义包格式
- 环境变量
- 退出码
- 常见问题
快速安装
如果只想直接使用 pbnpm 而不自行编译,可使用预编译二进制一键安装。脚本会从 pbpkg 仓库下载对应平台的 pbnpm 可执行文件并配置到本机 PATH,安装完成后即可在任意 PowerBuilder 工程目录(含 .pbt 文件)下直接调用 pbnpm 命令。
Windows(PowerShell):
iwr -useb https://gitee.com/paulajam/pbpkg/raw/master/p/pbnpm/install-pbnpm.ps1 | iex
Linux / macOS(bash,需 curl 或 wget):
curl -fsSL https://gitee.com/paulajam/pbpkg/raw/master/p/pbnpm/install-pbnpm.sh | bash
# 或
wget -qO- https://gitee.com/paulajam/pbpkg/raw/master/p/pbnpm/install-pbnpm.sh | bash
Linux 脚本下载 cosmocc 编译的 APE 统一二进制(pbnpm-cosmo,单一文件同时覆盖 x86_64 + aarch64),安装到 ~/pbnpm/ 并写入 ~/.bashrc(zsh 用户写 ~/.zshrc)。如当前会话未识别 pbnpm 命令,执行 source ~/.bashrc 或重开终端即可。
如需从源码自行编译,请参考下面的 编译与获取 章节。
重点:在 PB9 工程中安装 pblit
pblit 是面向 PowerBuilder 的原生数据库驱动包,可在 PB 的标准
Transaction、嵌入式 SQL 和 DataWindow 代码中连接 SQLite、MySQL 和
SQL Server。以 PowerBuilder 9.0 工程为例,进入 .pbt 所在目录后只需执行:
cd D:\work\myapp
pbnpm install pblit --pb 90
其中:
pblit是默认 pbpkg 仓库中的包名,等同于安装仓库下的p/pblit。--pb 90表示目标运行环境是 PowerBuilder 9.0,不是 pbnpm 或 pblit 的版本号。- pbnpm 会自动选择 PB9 制品,并把
pblit90.dll、sqlite3.dll、libmariadb.dll和ntwdblib.dll安装到工程根目录。 - pblit 包不含 PBL,因此不会修改
.pbt的 LibList;安装记录会写入pbnpm.lock,变更日志会写入pbnpm.log。
当前目录有多个 .pbt 时,明确指定要安装到哪个工程:
pbnpm install pblit --pb 90 --pbt myapp.pbt
安装后可运行 pbnpm list 检查记录。下面是 PB9 使用 SQLite 的最小示例:
/**
* --pbpkg pblit
* --pb-version 90
*/
string ls_sql
sqlca.DBMS = "LIT SQLite"
sqlca.Database = "demo.db"
sqlca.AutoCommit = true
connect;
if sqlca.SQLCode = 0 then
ls_sql = "create table if not exists users " + &
"(id integer primary key, name varchar(10))"
execute immediate :ls_sql;
insert into users(id,name) values(1,'你好');
insert into users(id,name) values(2,'pblit');
window w
open(w)
w.visible = false
datawindow dw_1
w.openuserobject(dw_1,'datawindow')
w.title = 'hello pblit'
w.resize(1204*3,768*3)
dw_1.resize(w.width,w.height)
string str,syn
syn = sqlca.SyntaxFromSQL('select * from users','Style(type=Grid)', str)
dw_1.create(syn, str)
dw_1.settransobject(sqlca)
dw_1.retrieve()
dw_1.visible = true
w.center = True
w.visible = true
end if
连接 MySQL 时将 DBMS 设置为 LIT MySQL,连接 SQL Server 时设置为
LIT SQLServer。发布应用时,应将安装得到的 pblit 驱动及对应数据库客户端
DLL 一并放到应用 EXE 同目录,确保 Windows 运行时能够加载它们。
编译与获取
pbnpm 使用 xmake 构建,同一份源码支持三种编译模式:Windows 32 位、Linux/macOS 原生 64 位、以及基于 cosmocc 的 Linux/macOS 统一 APE 二进制。
Windows(32 位 x86):
# 在 pbhelp 项目根目录执行
xmake f -P pbnpm -p windows -a x86 -m release
xmake -P pbnpm
或直接运行批处理:
cd pbnpm
xrun-310.bat
Linux / macOS(原生 64 位,分架构产出):
# 在 pbhelp 项目根目录执行,需要 g++ ≥ 5 / clang ≥ 5,支持 C++14
xmake f -P pbnpm -p linux -m release # macOS 用 -p macosx
xmake -P pbnpm
Linux/macOS 统一 APE 二进制(推荐,单文件覆盖 x86_64 + aarch64):
需要先下载 cosmocc 4.0.x 工具链并解压到本地(如 /opt/cosmocc),然后:
# 在 pbhelp 项目根目录执行
xmake f -P pbnpm -p cosmocc -m release --cosmocc=/opt/cosmocc
xmake -P pbnpm
也可通过环境变量指定 cosmocc 路径:
export COSMOCC_HOME=/opt/cosmocc
xmake f -P pbnpm -p cosmocc -m release
xmake -P pbnpm
编译产物:
| 平台 | 产物路径 | 说明 |
|---|---|---|
| Windows | pbnpm/dist/pbnpm/pbnpm.exe |
32 位 x86,可调用 PBSpy.dll |
| Linux / macOS 原生 | pbnpm/dist/pbnpm/pbnpm |
当前架构原生 ELF |
| cosmocc 统一 APE | pbnpm/dist/pbnpm/pbnpm-cosmo |
单文件覆盖 x86_64 + aarch64,Linux/macOS 通用 |
关于 APE 二进制:APE(Actually Portable Executable)是 cosmopolitan 项目的 polyglot 格式,同一文件可在 Linux/macOS/FreeBSD 等 x86_64 + aarch64 系统上原生运行。Linux 系统首次运行 APE 时建议安装 ape loader(
sudo wget -O /usr/bin/ape https://cosmo.zip/pub/cosmos/bin/ape-$(uname -m).elf && sudo chmod +x /usr/bin/ape),否则部分系统可能因不识别格式而报exec format error。
把它复制到任意 PowerBuilder 工程目录(含 .pbt 文件)下即可使用,也可加入 PATH 全局调用。
运行前置条件
| 项 | 要求 |
|---|---|
| 操作系统 | Windows 32 位(控制台代码页 GBK / 936);Linux / macOS 64 位也可运行(见下方平台差异) |
| 工程文件 | 安装含 PBL 的包及 uninstall / check 时,当前目录至少存在一个 .pbt(或通过 --pbt 指定);python34_full 这类不含 PBL 的运行时包可直接安装到无 .pbt 的目录 |
| git | 远程 install 优先使用 Git 并缓存仓库;未安装 Git 时回退 HTTP/raw。upload 和远程模板 create 仍需要系统 PATH 中可调用 git;纯本地操作不依赖 git |
| PBORCA | create 命令通过 PBSpy.dll 生成工程 PBL;未安装时自动回退到最小 PBL(Linux/macOS 也可跑通 create) |
| pypower 环境 | test 优先使用当前目录 pbl_modules/python34_full;也可设置 PBNPM_PYPOWER_ENV,均无效时自动下载到缓存 |
PBORCA/PBSpy DLL 查找顺序
create 编译模板 PBL 时按以下顺序查找 PBSpy.dll / pborca<版本>.dll:
- 环境变量
PBNPM_PBORCA_DLL指定的路径(最高优先级,覆盖下面所有候选) - pbnpm 可执行文件同目录
%USERPROFILE%\.pbpkg\runtime\pb<版本>\bin\(pypower/pbnpm 共用的运行时目录约定)- 系统 PATH 中的
pbspy.dll/pborca<版本>.dll
加载 ORCA DLL 后会先预加载同目录的 pbvm<内部码>.dll(如 PB9 → pbvm90.dll),并将该目录加入进程 DLL 搜索路径。
支持的 PowerBuilder 版本(--pb 参数):
80/90/100/105/110/115/120/125/126/2017/2018/2019/2021/2022/2025
例如 90 对应 PB 9.0、2017 对应 PB 2017(PBVM 内部版本号 170)。
平台差异
| 能力 | Windows | Linux / macOS |
|---|---|---|
install / uninstall / list / check / cache |
✅ | ✅ |
upload |
✅ | ✅ |
create(远程/本地模板、依赖安装、PBW 生成) |
✅ | ✅ |
create 编译模板 PBL(PBORCA) |
✅ 通过 PBSpy.dll | ⚠️ 无 PBORCA,自动回退到最小 PBL 生成器 |
install --exe / uninstall --exe(内嵌 ob.exe) |
✅ | ❌ 仅 Windows 支持 |
test(pypower 单元测试) |
✅ | ✅(自动下载 Linux 版 python34_full 环境) |
命令总览
pbnpm install <url|包名|本地目录|本地PBL> --pb <版本> [--tag <发行版本>] [--object <对象名>] [--exe <程序>]
pbnpm uninstall <包名> [--yes] [--exe <程序>]
pbnpm list
pbnpm upload <文件夹> --tag <发行版本> [--token <token>]
pbnpm create [模板目录|模板名] --name <项目名> [--pb <版本>]
pbnpm check
pbnpm test [<文件|目录>...] [--pb-version <版本[,版本...]>] [选项]
pbnpm cache <list|clean|path> [--pkg <包名>]
pbnpm help
公共参数:
| 参数 | 说明 |
|---|---|
--pbt <文件名> |
指定 .pbt 文件(当前目录有多个 .pbt 时使用,或直接给绝对/相对路径) |
--pb <版本> |
PowerBuilder 主版本号,install/create 必传;create 默认 90 |
--force / -f |
install:已存在同名文件时强制覆盖;create:目标目录已存在时合并覆盖 |
--no-cache |
install:跳过缓存读写,强制走网络下载 |
--shared <目录> |
install:从共享文件夹解析包名或相对包路径;别名:--share、--shared-folder、--shared-dir |
--yes / -y |
uninstall:跳过确认提示 |
-v / --verbose |
全局选项:输出详细日志(包括 [信息] 级别的进度消息);不带此标志仅显示最终结果和错误 |
install — 安装包
pbnpm install <来源> --pb <版本> [--tag <发行版本>] [--object <对象名>] [--exe <程序>]
[--force] [--no-cache] [--pbt <文件名>] [--shared <共享文件夹>]
来源的 7 种形式(自动识别)
| 形式 | 示例 | 行为 |
|---|---|---|
| ① 包目录 URL | https://gitee.com/x/pbpkg.git/p/pbmss |
优先通过 Git 获取包目录并复用仓库缓存;无 Git 时回退 raw |
| ② 单个 PBL URL | https://gitee.com/x/pbpkg.git/o/ole/ole.pbl |
优先通过 Git 获取该文件;无 Git 时回退 raw |
| ③ 简短包名 | pbmss、pbidea、easypj |
等同于默认仓库 https://gitee.com/paulajam/pbpkg.git 的 <首字母>/<包名>(例如 e/easypj) |
| ④ PBL 相对路径 | ole/ole.pbl |
自动按默认仓库布局解析为 o/ole/ole.pbl |
| ⑤ 本地 PBL 文件 | C:\path\to\websuite.pbl |
直接复制到目标 pbl_modules/,常配合 --exe 使用 |
| ⑥ 本地包目录 | C:\path\to\easypj |
与远程包目录结构相同,跳过 git 下载直接安装 |
| ⑦ 共享文件夹 | \\server\share\pbpkg + pbidea |
从共享 pbpkg 根目录解析包名后安装;支持 UNC 路径 |
参数
| 参数 | 必传 | 说明 |
|---|---|---|
--pb <版本> |
是 | PowerBuilder 目标版本;按 pbArtifacts 兼容规则或 <pb_version>.zip 选择制品 |
--tag <发行版本> |
否 | git tag(如 v20250819);不传则用 master 最新 |
--object <对象名> |
否 | 仅安装指定对象及依赖(自动解析依赖闭包),逗号分隔多个,如 uo_json,uo_webview2;部分安装生成的 PBL 使用包名命名。对象级安装优先通过 PBORCA_LibraryEntryCopy 从源 PBL 复制已编译二进制对象(速度快、保留原始编译产物),失败时回退到源码重新编译,ORCA 不可用时再回退到最小 PBL 生成器 |
--exe <程序> |
否 | 安装到 EXE 同目录并把 PBL 路径/对象写入嵌入 ob.exe(与 --pbt 互斥) |
--force / -f |
否 | 已存在同名文件时强制覆盖 |
--no-cache |
否 | 跳过缓存读写(强制走网络下载) |
--pbt <文件名> |
否 | 指定 .pbt,与 --exe 互斥 |
当前目录没有 .pbt 时,install 仍会下载并检查包内容:不包含顶层 PBL 的运行时包可正常安装,并在当前目录写入 pbl_modules/、pbnpm.lock 和 pbnpm.log;若包包含 PBL,则在复制文件前报错退出。
安装流程
- 解析来源,确定仓库地址 / 子路径 / 包名
- 解析
.pbt(或读取--exe内嵌的 ob.exe 清单);未找到.pbt时先以当前目录作为目标,随后只允许安装不含 PBL 的包 - 若包目录含
pbnpm.json,先递归安装直接依赖 - 按下载模式获取文件:Git checkout 后处理单文件、ZIP 或文件集;无 Git 时回退 raw。文件夹模式在
<目录>/<pb>.zip不存在时,优先使用<子路径>/<pb版本>子目录,仍不存在才使用整个<子路径>;若获取内容里含与包名或 artifact 路径匹配的内嵌.zip,会自动解压到当前目录并删除原 zip - 拷贝文件到目标目录(默认
pbl_modules/),按需追加.pbt的 LibList - PB ≤ 90 兼容处理:对 pbvm 内部码 ≤ 90 的目标版本(PB8/9),自动移除 PB 源码中的
;ansi后缀(PB100+ 引入的标记,低版本无法解析,包缓存保留原始文件以供后续更高版本安装);PB80 检测到部分对象依赖longlong时,向目标 PBL 注入兼容的longlong.srs/longlong.srf定义,仅用于通过编译,实际执行相关运算仍可能异常 - 动态库别名:若包的
pbnpm.json声明dllRenames,按目标 PB 版本复制或重命名对应 DLL;可用keepOriginal控制是否保留源文件 - 许可证管理:若包目录含
license.json,自动同步到用户目录(详见许可证管理) - 扫描所有 PBL 检测重复对象名(
--exe模式跳过) - 写入
pbnpm.lock安装记录、追加pbnpm.log变更日志 - 若启用
--exe,原子替换 EXE 内嵌 ob.exe
示例
:: 从默认仓库安装简短包名
pbnpm install pbidea --pb 90
:: 安装指定发行版本(启用永久缓存)
pbnpm install pbidea --pb 90 --tag v20250819
:: 跳过缓存
pbnpm install pbidea --pb 90 --tag v20250819 --no-cache
:: 只安装指定对象及其依赖(自动解析依赖闭包)
pbnpm install pbidea --pb 90 --object uo_json
pbnpm install pbidea --pb 90 --object uo_json,uo_webview2
:: 安装到已编译 EXE
pbnpm install pbidea --pb 90 --exe calc.exe
:: 安装本地 PBL 到同目录 EXE
pbnpm install C:\path\to\websuite.pbl --pb 90 --exe C:\path\to\calc.exe
:: 安装本地包目录
pbnpm install C:\path\to\easypj --pb 90
:: 从共享 pbpkg 根目录安装包(Windows UNC 路径)
pbnpm install pbidea --shared \\server\share\pbpkg --pb 90
共享目录执行 `--object` 时,若包中包含二进制 `.pbl`,pbnpm 会先随机读取 PBL
索引,只按需读取目标对象及其依赖源码,再生成部分 PBL;不会先把整个 PBL 或包复制到本地。
运行时 DLL 仍会按包完整复制,ZIP 制品仍需先解压后分析。
:: 完整 URL
pbnpm install https://gitee.com/paulajam/pbpkg.git/p/pbidea --pb 90
pbnpm install https://gitee.com/paulajam/pbpkg.git/o/ole/ole.pbl --pb 90
:: 当前目录有多个 .pbt 时指定
pbnpm install pbidea --pb 90 --pbt app.pbt
uninstall — 卸载包
pbnpm uninstall <包名> [--yes] [--exe <程序>] [--pbt <文件名>]
读取 pbnpm.lock 中该包的文件清单,删除属于该包的文件;被其他已安装包共享的文件会自动跳过。同时:
- 从
.pbt的 LibList 移除对应 PBL 路径 - 若启用
--exe,从内嵌 ob.exe 移除对应 PBL 路径与对象名(共享对象自动保留) - 更新
pbnpm.lock(删除该条目)与pbnpm.log
示例:
pbnpm uninstall pbidea
pbnpm uninstall pbidea --yes :: 跳过确认
pbnpm uninstall pbidea --exe calc.exe --yes :: 从 EXE 内嵌 ob.exe 移除
注意:uninstall 不会自动卸载该包引入的依赖包,需手动卸载。
list — 列出已安装包
pbnpm list [--pbt <文件名>]
读取 pbnpm.lock,列出每个已安装包的:
- 目标 PB 版本 / 实际 artifact / 发行版本(tag 或 master)
- 直接依赖列表
- 文件数与文件清单(超过 5 个时省略)
- 安装时间戳
pbnpm list
upload — 上传包到仓库
pbnpm upload <文件夹> --tag <发行版本> [--token <token>] [--user <用户名>]
[--repo <url>] [--name <包名>] [--message <msg>]
把本地文件夹内容上传到 pbpkg 仓库的 <首字母>/<包名>/ 目录(如 pbidea → p/pbidea,Websuite → w/websuite),并在 master 分支上打 tag 推送。
参数
| 参数 | 必传 | 默认值 | 说明 |
|---|---|---|---|
<文件夹> |
是 | — | 本地待上传文件夹 |
--tag <发行版本> |
是 | — | git tag(如 v20250819) |
--token <token> |
否 | 环境变量 PBNPM_GIT_TOKEN 或 GITEE_TOKEN |
gitee access token |
--user <用户名> |
否 | paulajam |
gitee 用户名(用于 URL 鉴权) |
--repo <url> |
否 | https://gitee.com/paulajam/pbpkg.git |
仓库地址 |
--name <包名> |
否 | 取文件夹名 | 指定包名(决定仓库内目标子路径) |
--message <msg> / -m |
否 | upload <包名> <tag> |
commit message |
流程
- clone 仓库 master 分支到临时目录
- 拷贝文件夹内容到
<首字母>/<包名>/ - commit、打 tag、推送 master + tag
示例:
pbnpm upload ./pbidea --tag v20250819 --token xxx
pbnpm upload ./websuite --tag v20250819 --name ws --token xxx
pbnpm upload ./mylib --tag v20250819 --user paulajam --repo https://gitee.com/me/myrepo.git
安全提示:token 不会打印到日志,URL 中鉴权信息仅在调用 git 时临时注入。
create — 从模板创建工程
pbnpm create [模板目录|模板名] --name <项目名> [--pb <版本>]
从模板创建一个新的 PowerBuilder 工程。
参数
| 参数 | 必传 | 说明 |
|---|---|---|
[模板目录] 或 [模板名] |
否(默认 tpl_app) |
本地模板目录路径,或默认仓库的模板名(如 tpl_app → 默认仓库 t/tpl_app,自动下载) |
--name <项目名> |
是 | 仅含字母/数字/下划线;在当前目录创建同名空目录作为工程目录 |
--pb <版本> |
否 | 安装模板 pbnpm.json 依赖时使用,默认 90 |
--force / -f |
否 | 目标目录已存在时合并覆盖已有内容(含同名文件/目录冲突);不传则在非空目录上直接报错 |
流程
- 加载模板(本地目录或从默认仓库下载到临时目录)
- 校验模板:必须只含一个
.pbt,且.pbt中声明的 PBL 路径必须是模板目录内的相对.pbl路径且真实存在 - 把模板目录复制到
<当前目录>/<项目名>/,递归替换文件名与文件内容中的模板名为项目名(含 NUL 字节的二进制文件跳过替换) - 安装模板
pbnpm.json声明的依赖(远程默认模板会立即校验 master 缓存,确保使用最新包对象;依赖必须在编译模板 PBL 之前安装,以便 PBORCA 解析继承与外部对象) - 通过 PBORCA 把模板中的 PBL 源码目录编译成真实 PBL(PBORCA 不可用时回退到最小 PBL 生成器,支持 ANSI 与 Unicode 两种模式)
- 生成
<项目名>.pbw工作区文件
示例:
:: 从本地模板创建
pbnpm create C:\path\to\tpl_app --name test1
:: 从默认仓库模板创建
pbnpm create tpl_app --name test1
:: 省略模板名时默认使用 tpl_app
pbnpm create --name test1
check — 检查重复对象名
pbnpm check [--pbt <文件名>]
仅扫描 .pbt 中所有 PBL,检测重复对象名(同名对象出现在多个 PBL 中会导致 PB 编译冲突)。不修改任何文件,只写一条 pbnpm.log 记录。
退出码:无重复返回 0,发现重复返回 2。
test — 运行 pypower 单元测试
pbnpm test [<文件|目录>...] [--pb-version <版本[,版本...]>] [选项]
封装并调用 pypower test 子命令运行 .pbtest 单元测试。显式传入 .md 或 .markdown 文件时,pypower test 会提取其中的 powerscript、powerbuilder、pbtest 围栏代码块;包含 test_* 子程序的围栏提供测试用例,所有围栏的共享定义会合并并注入每个用例。完全不含 test_* 的围栏只保留外部 DLL 原型和短源码块,普通执行语句不会运行。多个代码块中的 --pbpkg 会按包名和对象列表保序去重,并加入当前测试组统一安装。除 --output、--env-only / --update-env 外,所有参数原样透传给 pypower,由 pypower 的 argparse 解析。--pb-version 传入逗号分隔列表时由 pbnpm 拦截,并为每个版本启动独立子进程依次测试,最终任一版本失败即返回 1。
pbnpm 拦截的选项
| 选项 | 说明 |
|---|---|
--output <文件> |
将 pbnpm、嵌入式 Python 以及 pypower test 的标准输出和错误输出全部写入指定文件;该参数不会透传给 pypower |
--env-only |
仅下载/检测 pypower 环境,不执行测试 |
--update-env |
强制重新下载 pypower 环境。仅在当前命中的环境来自通用缓存(<cache_root>/pypower-env/)时才触发清理重下;若环境来自工程本地 pbl_modules/python34_full、PBNPM_PYPOWER_ENV 或包缓存,则不会强制重下 |
--pb-version 90,100 |
在独立 pbnpm 子进程中依次运行 PB 90、PB 100 测试;版本按输入顺序执行,重复版本自动去重 |
透传给 pypower 的常用选项(示例)
--pb-version <版本> 单个 PB 版本
--pbt <文件.pbt> 指定 .pbt 工程文件
--pbl <my.pbl> 额外注入 PBL(可重复)
--filter <关键字> 仅运行名称含关键字的用例
--copy-from a.pbl;w_xxx 拷贝对象到测试 PBL
--dll-dir ./dlls 指定 PBVM/PBORCA dll 目录
pypower 环境查找顺序
- 当前目录
./pbl_modules/python34_full/ - 环境变量
PBNPM_PYPOWER_ENV指向的目录(如vscode-pypower/bin/python34_full) - 包缓存目录
<cache_root>/<repo>/p/python34_full/<pb>/<ref>/files/(pbnpm install 后的缓存) - 缓存目录
<cache_root>/pypower-env/python34_full/ - 都没有有效环境 → 自动从默认仓库
p/python34_full/python34_full.zip下载并解压
加载 python34.dll 前,pbnpm 会把所选 python34_full 目录加入当前进程的 DLL 搜索路径,以便解析同目录依赖。加载失败时会输出 Windows 错误码:126 通常表示依赖 DLL 缺失,193 通常表示 Python DLL 与 32 位 pbnpm 位数不一致。
示例
:: 运行单个 .pbtest
pbnpm test foo.pbtest --pb-version 90
:: 运行 Markdown 中的 PowerBuilder 代码块
pbnpm test guide.md --pb-version 90
:: 将 pbnpm、Python 和测试的全部输出写入文件
pbnpm test foo.pbtest --pb-version 90 --output test.log
:: 使用独立 EXE 运行并打包可运行环境
pbnpm test foo.pbtest --pb-version 90 --build-exe --output-zip d:\output.zip
:: 运行目录下所有 .pbtest
pbnpm test ./tests --pb-version 90
:: 同一批用例依次在 PB 90、PB 100 中测试
pbnpm test ./tests --pb-version 90,100
:: 仅运行名称含 test_bar 的用例
pbnpm test foo.pbtest --pb-version 90 --filter test_bar
:: 指定 .pbt 工程文件
pbnpm test foo.pbtest --pb-version 90 --pbt app.pbt
:: 仅准备环境
pbnpm test --env-only
:: 强制更新环境
pbnpm test --update-env
cache — 管理下载缓存
pbnpm cache list
pbnpm cache clean [--pkg <包名>]
pbnpm cache path
| 子命令 | 说明 |
|---|---|
list |
列出所有缓存条目(包名 / PB 版本 / artifact / 发行版本 / commit / 大小 / 时间) |
clean |
清理整个缓存;加 --pkg <包名> 仅清理指定包的缓存 |
path |
显示缓存根目录路径 |
缓存位置
- Windows:
%LOCALAPPDATA%/pbnpm/cache/ - Linux:
~/.cache/pbnpm/(或$XDG_CACHE_HOME/pbnpm/)
缓存目录结构
<cache_root>/
└── <sanitized_repo>/ # 如 gitee.com_paulajam_pbpkg
└── <bucket>/ # 包名首字母小写(如 p)
└── <pkg>/ # 包名(如 pbidea)
└── <pb_version>/ # PB 版本(如 90)
└── <ref>/ # tag 名(如 v20250819)或 master/latest/
├── files/ # 解压后内容(可直接拷贝到 pbt_dir)
└── meta.ini # 缓存元信息
meta.ini 记录包名、仓库、子路径、PB 版本、实际 artifact、ref、commit hash(master 模式)、文件数、大小、缓存时间、上次校验时间。
示例:
pbnpm cache list
pbnpm cache clean --pkg pbidea
pbnpm cache path
包清单(pbnpm.json)
包目录可包含 UTF-8 编码的 pbnpm.json,用于声明直接依赖、不同 PowerBuilder 版本使用的制品和动态库别名。
{
"dependencies": {
"pbmss": "v20260711",
"pbidea": {
"tag": "",
"objects": ["uo_json", "uo_webview2"]
}
},
"pbArtifacts": {
"90": "90.zip",
">=100": "100.zip",
"<=90": "legacy.zip"
},
"dllRenames": {
"easypj.dll": {
"keepOriginal": true,
">=80": "pbesy{pb}.dll",
">=2019": "pbesy.dll"
},
"pblit100.dll": {
"keepOriginal": false,
">=105": "pblit{pbvm}.dll",
">=2019": "pblit.dll"
}
}
}
| 字段 | 说明 |
|---|---|
dependencies |
对象,键为依赖包名;值可为原有的版本标签/本地路径字符串,或部分对象配置 |
dependencies.<包名>.tag |
可选,版本标签或本地路径;空值继承父包的 --tag(或 master) |
dependencies.<包名>.objects |
非空字符串数组,只安装指定对象及它们在该包内的依赖闭包 |
pbArtifacts |
对象,键为 PB 版本选择器,值为包目录内的相对 .zip 路径,或不压缩的相对文件路径数组 |
dllRenames |
对象,键为源 DLL;值为别名模板字符串,或包含 keepOriginal 与 PB 版本选择器的模板对象 |
值 ""(空字符串) |
继承父包的 --tag(或 master) |
值为 tag(如 v20260711) |
使用该 tag 安装 |
值为绝对目录(如 C:\\path\\to\\python34_full) |
从本地目录安装该依赖 |
| 本地包的空值或 tag | 从同一 pbpkg 仓库按 <首字母>/<包名> 定位 |
约束:
- 依赖继承 install 的
--pb objects使用与install --object相同的对象名格式,可带.sr*后缀;运行时 DLL 仍按包完整安装- 未配置
objects时仍安装整个依赖包;父包命令行的--object不会传给子依赖 - 自动检测循环依赖
uninstall不会自动删除依赖包,需手动卸载pbArtifacts支持三种选择器:精确版本(如"90")、最低版本(如">=100")、最高版本(如"<=90")- 选择器匹配优先级:精确版本 >
>=最低版本 ><=最高版本;多个>=同时匹配时选起始版本最高的规则,多个<=同时匹配时选起始版本最低的规则 - PB 版本按内部 PBVM 顺序比较,例如
2022的排序值为220 - 声明了
pbArtifacts但没有匹配规则或制品不存在时安装失败,不回退整个包目录 - 未声明
pbArtifacts的旧包继续使用<pb_version>.zip和原有目录回退行为 dllRenames默认保留源 DLL 并复制别名;模板对象可用keepOriginal: false改为重命名,支持{pb}和{pbvm}占位符dllRenames对象形式使用与pbArtifacts相同的版本选择器和优先级pbArtifacts文件数组会逐个下载/复制所列文件,不会生成或解压 ZIP;适合按 PB 版本拆分运行时 DLL
pbnpm.lock 安装记录
每次 install 成功后写入 .pbt 同目录的 pbnpm.lock(INI 风格,UTF-8),uninstall 时读取并删除对应条目。
[package pbidea]
pb_version=90
artifact=90.zip
tag=v20250819
files=PbIdea.dll;sql.pbl;websuite.pbl
installed=2025-07-13 10:30:00
[package mylib]
pb_version=90
tag=master
files=mylib.pbl
installed=2025-07-13 11:00:00
每条记录包含:包名、目标 PB 版本、清单实际选择的 artifact(旧包为空)、tag(或 master)、直接依赖列表、安装时拷贝的文件名、--object 安装时的对象名与原始过滤值、安装时间戳。
pbnpm.log 日志
所有命令执行后追加写入 .pbt 同目录的 pbnpm.log(GBK 编码,与 PB 一致)。每条记录包含命令名、PB 版本、操作详情(新增/替换 PBL、增删对象、错误等)。
缓存机制
install 默认启用缓存,避免重复下载:
| 模式 | 触发条件 | 策略 |
|---|---|---|
| tag 模式 | --tag <发行版本> |
永久缓存(tag 不可变,永久命中) |
| master 模式 | 不传 --tag |
TTL 1 小时 + git ls-remote commit hash 校验 |
master 模式详细流程
- 上次
ls-remote校验在 1 小时内 → 直接信任缓存,不调用git ls-remote - 超过 1 小时 → 调用
git ls-remote <repo> <ref>校验 commit hash - hash 相同 → 更新
last_check_at,命中缓存 - hash 不同 → 上游已变化,使用 Git fetch/reset 更新仓库缓存,再处理包内容
ls-remote失败(如网络故障)→ 保守信任缓存,命中
写入串行化
写入缓存时使用文件锁(FileLock,RAII 封装,支持超时),防止多进程同时写同一 cache_dir。进程退出时 OS 自动释放锁。写入采用 staging 目录 + 原子 rename 策略,避免半成品被其他进程读到。
跳过缓存
加 --no-cache 强制走网络下载,不读不写缓存。
EXE 内嵌 ob.exe 模式
install --exe <程序> 与 uninstall --exe <程序> 直接操作已编译 PB EXE 末尾追加的 ob.exe 二进制清单:
install --exe:把新增 PBL 路径(正斜杠序列列化)和对象名追加到清单,原子替换 EXEuninstall --exe:从清单移除属于该包的 PBL 路径与对象名,被其他包共享的对象自动保留
--exe 与 --pbt 互斥;使用 --exe 时跳过 .pbt 解析与重复对象检查。
示例:
pbnpm install pbidea --pb 90 --exe calc.exe
pbnpm install C:\path\to\websuite.pbl --pb 90 --exe C:\path\to\calc.exe
pbnpm uninstall pbidea --exe calc.exe --yes
许可证管理
若包目录包含 license.json,install 命令会自动将其同步到当前用户的 pypower 许可证目录:
- Windows:
%USERPROFILE%/.pypower/license.json - Linux/macOS:
$HOME/.pypower/license.json
同步规则:保留有效期更长的许可证。若已有许可证格式无效,自动用包中的许可证替换。
许可证文件格式示例(UTC 时间):
{
"expire": "2026-12-31T23:59:59Z",
"package": "pbidea",
"user": "user"
}
PBL 源码提取
pbnpm 内置了从二进制 PBL 文件中提取源码的能力(pbl_extractor),支持 ANSI/GBK 与 Unicode(UTF-16-LE)两种 PBL 格式。这是 --object 安装时依赖解析和对象检测的基础设施,也用于 create 命令中最小 PBL 生成器的源码写入。
相关功能:
- 对象名扫描:遍历 PBL 的 B 树索引,提取所有 ENT* 条目(对象名、数据块偏移),支持 PB8 ~ PB2022
- 源码提取:沿 DAT* 链遍历拼接对象数据,切掉注释头后按编码解码
- 依赖分析:从
.sr*源码中提取类型依赖(type from X、type variables、函数参数),递归求解依赖闭包,按拓扑序输出 - 短格式源码扩展:支持将
@object.sr*.header; ... @end简写语法展开为完整 PB 源码
自定义包格式
把一份 PowerBuilder 库打包成 pbnpm 可识别的"包",只需按下面的目录布局组织文件,可选地加上 pbnpm.json 清单,再用 pbnpm upload 推到仓库,或直接本地路径 pbnpm install 安装。
最小可用包
仅含一个 PBL 文件即可成为一个包:
mylib/
└── mylib.pbl
安装:
pbnpm install D:\path\to\mylib --pb 90
:: 或上传到仓库
pbnpm upload D:\path\to\mylib --tag v20260811 --token xxx
推荐目录结构
mylib/
├── pbnpm.json # 可选,声明依赖与多版本制品
├── license.json # 可选,许可证(自动同步到用户目录)
├── mylib.pbl # 二进制 PBL(PB9 编译产物,可直接安装)
├── mylib/ # 或源码目录形式(与 .pbl 同名,内含 .sr* 源码)
│ ├── uo_json.sru
│ ├── n_crypto.sru
│ └── gf_helper.srf
├── mylib.dll # 可选,运行时依赖 DLL(安装到工程根目录)
├── 90.zip # 可选,PB9 专用制品(pbArtifacts 引用)
└── 100.zip # 可选,PB10+ 专用制品
文件分发规则
install 按文件类型自动分发到目标工程的不同位置:
| 文件类型 | 安装目标 | 说明 |
|---|---|---|
.pbl |
pbl_modules/ |
PB 库文件,自动追加到 .pbt 的 LibList |
.pbl 同名目录(源码形式) |
pbl_modules/ |
内含 .sr* 源码,安装时由 PBORCA 编译为二进制 PBL |
.dll / .ocx |
工程根目录(与 .pbt 同级) |
运行时动态库,PB exe 同目录加载 |
.sra/.srb/.srd/.srf/.srj/.srm/.srn/.srp/.srq/.srs/.srt/.sru/.srw/.srx |
pbl_modules/ |
PB 源码文件(与同名 .pbl 目录等价) |
pbnpm.json |
不复制 | 仅作为清单解析 |
license.json |
不复制 | 同步到 %USERPROFILE%/.pypower/license.json |
其他未识别的文件类型也会复制到
pbl_modules/,但建议包内只放上述 PB 相关文件。
PBL 的两种形式
包内的 PBL 可以用两种等价形式提供,pbnpm 会自动处理:
- 二进制 PBL 文件(推荐用于分发):
mylib.pbl直接作为编译好的库安装 - 源码目录形式:
mylib/目录(与 PBL 同名,无扩展名),内含.sr*源码文件,安装时通过 PBORCA 编译为二进制 PBL;PBORCA 不可用时回退到最小 PBL 生成器
两种形式不要同时存在;若同时存在,源码目录会被优先编译为二进制 PBL。
多版本制品(pbArtifacts)
不同 PB 版本可能需要不同的 PBL 编译产物。在 pbnpm.json 中声明 pbArtifacts,让 pbnpm 按 --pb 选择对应 zip:
{
"pbArtifacts": {
"90": "90.zip",
">=100": "100.zip",
"<=90": "legacy.zip"
}
}
- 每个值是包目录内的相对
.zip路径(如90.zip、pb9/legacy.zip) - 选择器三种:精确版本(
"90")、最低版本(">=100")、最高版本("<=90") - 匹配优先级:精确 >
>=><= - 路径只能含字母/数字/
_/-/.//,不能以-开头,不能含.. - 声明了
pbArtifacts但无匹配规则或制品不存在时安装失败(不回退整个目录) - 不声明
pbArtifacts时,按<pb_version>.zip→ 整个包目录的顺序回退
90.zip 内部布局应与包目录一致(解压后顶层直接是 .pbl/.dll 等文件)。
动态库别名(dllRenames)
dllRenames 默认保留源 DLL,并按目标 PB 版本安装一个别名副本。字符串值适用于
所有 PB 版本;对象值可包含 keepOriginal,并使用与 pbArtifacts 相同的精确、>=、<= 选择器优先级。
{
"dllRenames": {
"easypj.dll": {
"keepOriginal": true,
">=80": "pbesy{pb}.dll",
">=2019": "pbesy.dll"
},
"pblit100.dll": {
"keepOriginal": false,
">=105": "pblit{pbvm}.dll",
">=2019": "pblit.dll"
}
}
}
{pb}展开为--pb的值;{pbvm}展开为内部运行时版本,例如2017展开为170。- 源和目标必须是包顶层的安全
.dll文件名;源文件缺失、文件名不安全或目标冲突时安装失败。
依赖声明
pbnpm.json 的 dependencies 声明当前包依赖的其他包,安装时会递归处理:
{
"dependencies": {
"pbmss": "v20260711",
"pbidea": {
"tag": "",
"objects": ["uo_json", "uo_webview2"]
},
"mylib-local": "C:\\path\\to\\mylib"
}
}
| 值形式 | 含义 |
|---|---|
""(空字符串) |
继承父包的 --tag(或 master) |
"v20260711" |
使用该 tag 安装 |
"C:\\path\\to\\pkg" |
从本地目录安装该依赖 |
{"tag":"...","objects":[...]} |
对象配置:tag 可选,objects 仅安装指定对象及其依赖闭包 |
约束:
- 依赖继承 install 的
--pb - 自动检测循环依赖
uninstall不会自动删除依赖包,需手动卸载objects使用与install --object相同的对象名格式,可带.sr*后缀
许可证文件(可选)
包目录含 license.json 时,install 自动同步到用户目录(详见许可证管理)。格式:
{
"expire": "2026-12-31T23:59:59Z",
"package": "mylib",
"user": "user"
}
打包与上传流程
- 按上述结构组织本地文件夹(文件夹名即默认包名)
- 若有多版本制品,准备对应的
<pb>.zip并在pbnpm.json中声明 - 上传到 pbpkg 仓库:
bat
pbnpm upload D:\path\to\mylib --tag v20260811 --token <gitee_token>
- 上传后包会出现在仓库的
<首字母>/<包名>/目录(如m/mylib) - 其他人即可通过
pbnpm install mylib --pb 90安装
本地包(不走 git)
不想上传到仓库时,直接用本地路径安装即可,目录结构与远程包完全一致:
pbnpm install D:\path\to\mylib --pb 90
pbnpm install D:\path\to\mylib\mylib.pbl --pb 90
本地路径模式不依赖 git,pbnpm.json 的依赖解析、pbArtifacts 制品选择、许可证同步等行为与远程安装完全一致。
环境变量
| 变量 | 说明 |
|---|---|
PBNPM_GIT_TOKEN |
upload 命令的 gitee access token(也可用 GITEE_TOKEN,命令行 --token 优先) |
GITEE_TOKEN |
同上,作为 --token 的备选环境变量 |
PBNPM_PYPOWER_ENV |
当前工程没有有效 pbl_modules/python34_full 时,使用该目录下的环境(如 vscode-pypower/bin/python34_full) |
PBNPM_PBORCA_DLL |
显式指定 create 编译模板 PBL 使用的 PBSpy.dll / pborca<版本>.dll 路径,最高优先级,覆盖默认查找路径 |
退出码
| 码 | 含义 |
|---|---|
0 |
命令执行成功(check 未发现重复对象) |
1 |
命令执行失败(参数错误、网络错误、git 操作失败等) |
2 |
check 发现重复对象名 |
| 其他 | test 命令透传 pypower 的退出码 |
常见问题
Q1: 当前目录有多个 .pbt 怎么办?
直接运行命令会提示输入序号选择;也可用 --pbt <文件名> 直接指定。
Q2: install 报 "缺少必传参数 --pb"?
--pb 是 install 必传参数,表示目标 PB 版本。包声明 pbArtifacts 时按兼容规则选择制品,否则定位 <pb_version>.zip。支持的版本见 运行前置条件。
Q3: 如何跳过缓存强制重新下载?
加 --no-cache,或先用 pbnpm cache clean --pkg <包名> 清理该包缓存。
Q4: --tag 模式与 master 模式有何区别?
--tag:tag 不可变,缓存永久命中,速度最快- 不传
--tag(master 模式):1 小时 TTL 内复用缓存,超过则git ls-remote校验 commit hash
Q5: uninstall 会删除依赖包吗?
不会。uninstall 只删除指定包自身的文件,依赖包需手动卸载。
Q6: --exe 模式与 --pbt 模式有何区别?
--pbt(默认):修改.pbt的 LibList,安装到工程目录--exe:不修改.pbt,直接读写已编译 EXE 末尾的内嵌ob.exe清单,安装到 EXE 同目录
两者互斥。
Q7: create 命令在 Linux/macOS 上能用吗?
可以。PBORCA/PBSpy 不可用时自动回退到最小 PBL 生成器,模板拷贝、依赖安装、PBW 生成都能正常完成。
Q8: 控制台中文显示乱码?
pbnpm 在 Windows 上设置控制台代码页为 936(GBK),与 PB 字符串编码一致。如果你的终端默认不是 GBK,请使用 cmd.exe 或 chcp 936 切换。
Q9: test 命令首次运行很慢?
先检查当前目录的 pbl_modules/python34_full,再检查 PBNPM_PYPOWER_ENV、包缓存目录和通用缓存。都无效时才会从默认仓库下载 python34_full.zip(约几十 MB),后续运行复用缓存。
Q10: 如何查看帮助?
pbnpm help
pbnpm --help
pbnpm -h
pbnpm /?
pbnpm
任意一种都可显示完整帮助。
Q11: 没装 git 或 git 命令不可用怎么办?
install / upload / create(远程模板)默认调用 git 从默认仓库拉取,若系统未安装 git 或 git 不在 PATH,可改用本地安装绕过:
- 浏览器手动下载仓库 master 分支压缩包:
https://gitee.com/paulajam/pbpkg/repository/archive/master.zip
- 解压到本地任意目录,例如
D:\pbpkg-master,解压后目录结构应为:
D:\pbpkg-master\
├── p\ # 各包目录(pbidea、pbmss、python34_full ...)
├── t\ # 模板目录(tpl_app ...)
└── index.json
- 直接用本地路径替代包名/模板名执行命令:
```bat :: 从本地模板创建工程 pbnpm create D:\pbpkg-master\t\tpl_app --name test
:: 从本地包目录安装 pbnpm install D:\pbpkg-master\p\pbidea --pb 90
:: 从本地 PBL 文件安装 pbnpm install D:\pbpkg-master\o\ole\ole.pbl --pb 90 ```
本地路径模式不依赖 git,list / uninstall / check / cache 本来就不需要 git,可正常使用。后续若安装了 git,远程模式自动恢复可用。
Test EXE mode
pbnpm test keeps the existing PBVM path by default. Use --build-exe to
compile a standalone pbtest_runner.exe before running the suite, or
--no-build-exe to force PBVM execution. The options are forwarded to
pypower test and also apply when tests are split by PBT or PB version.
Use --output-zip <path> together with --build-exe to package the generated
EXE, PBL/PBD files, runtime DLLs, package DLLs, and available python34_full
environment into a portable ZIP. ZIP output currently requires one PBT group
and one PB version.