Skip to content

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/


目录


快速安装

如果只想直接使用 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.dllsqlite3.dlllibmariadb.dllntwdblib.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:

  1. 环境变量 PBNPM_PBORCA_DLL 指定的路径(最高优先级,覆盖下面所有候选)
  2. pbnpm 可执行文件同目录
  3. %USERPROFILE%\.pbpkg\runtime\pb<版本>\bin\(pypower/pbnpm 共用的运行时目录约定)
  4. 系统 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
③ 简短包名 pbmsspbideaeasypj 等同于默认仓库 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.lockpbnpm.log;若包包含 PBL,则在复制文件前报错退出。

安装流程

  1. 解析来源,确定仓库地址 / 子路径 / 包名
  2. 解析 .pbt(或读取 --exe 内嵌的 ob.exe 清单);未找到 .pbt 时先以当前目录作为目标,随后只允许安装不含 PBL 的包
  3. 若包目录含 pbnpm.json,先递归安装直接依赖
  4. 按下载模式获取文件:Git checkout 后处理单文件、ZIP 或文件集;无 Git 时回退 raw。文件夹模式在 <目录>/<pb>.zip 不存在时,优先使用 <子路径>/<pb版本> 子目录,仍不存在才使用整个 <子路径>;若获取内容里含与包名或 artifact 路径匹配的内嵌 .zip,会自动解压到当前目录并删除原 zip
  5. 拷贝文件到目标目录(默认 pbl_modules/),按需追加 .pbt 的 LibList
  6. PB ≤ 90 兼容处理:对 pbvm 内部码 ≤ 90 的目标版本(PB8/9),自动移除 PB 源码中的 ;ansi 后缀(PB100+ 引入的标记,低版本无法解析,包缓存保留原始文件以供后续更高版本安装);PB80 检测到部分对象依赖 longlong 时,向目标 PBL 注入兼容的 longlong.srs/longlong.srf 定义,仅用于通过编译,实际执行相关运算仍可能异常
  7. 动态库别名:若包的 pbnpm.json 声明 dllRenames,按目标 PB 版本复制或重命名对应 DLL;可用 keepOriginal 控制是否保留源文件
  8. 许可证管理:若包目录含 license.json,自动同步到用户目录(详见许可证管理
  9. 扫描所有 PBL 检测重复对象名(--exe 模式跳过)
  10. 写入 pbnpm.lock 安装记录、追加 pbnpm.log 变更日志
  11. 若启用 --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 仓库的 <首字母>/<包名>/ 目录(如 pbideap/pbideaWebsuitew/websuite),并在 master 分支上打 tag 推送。

参数

参数 必传 默认值 说明
<文件夹> 本地待上传文件夹
--tag <发行版本> git tag(如 v20250819
--token <token> 环境变量 PBNPM_GIT_TOKENGITEE_TOKEN gitee access token
--user <用户名> paulajam gitee 用户名(用于 URL 鉴权)
--repo <url> https://gitee.com/paulajam/pbpkg.git 仓库地址
--name <包名> 取文件夹名 指定包名(决定仓库内目标子路径)
--message <msg> / -m upload <包名> <tag> commit message

流程

  1. clone 仓库 master 分支到临时目录
  2. 拷贝文件夹内容到 <首字母>/<包名>/
  3. 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 目标目录已存在时合并覆盖已有内容(含同名文件/目录冲突);不传则在非空目录上直接报错

流程

  1. 加载模板(本地目录或从默认仓库下载到临时目录)
  2. 校验模板:必须只含一个 .pbt,且 .pbt 中声明的 PBL 路径必须是模板目录内的相对 .pbl 路径且真实存在
  3. 把模板目录复制到 <当前目录>/<项目名>/,递归替换文件名与文件内容中的模板名为项目名(含 NUL 字节的二进制文件跳过替换)
  4. 安装模板 pbnpm.json 声明的依赖(远程默认模板会立即校验 master 缓存,确保使用最新包对象;依赖必须在编译模板 PBL 之前安装,以便 PBORCA 解析继承与外部对象)
  5. 通过 PBORCA 把模板中的 PBL 源码目录编译成真实 PBL(PBORCA 不可用时回退到最小 PBL 生成器,支持 ANSI 与 Unicode 两种模式)
  6. 生成 <项目名>.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 会提取其中的 powerscriptpowerbuilderpbtest 围栏代码块;包含 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_fullPBNPM_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 环境查找顺序

  1. 当前目录 ./pbl_modules/python34_full/
  2. 环境变量 PBNPM_PYPOWER_ENV 指向的目录(如 vscode-pypower/bin/python34_full
  3. 包缓存目录 <cache_root>/<repo>/p/python34_full/<pb>/<ref>/files/(pbnpm install 后的缓存)
  4. 缓存目录 <cache_root>/pypower-env/python34_full/
  5. 都没有有效环境 → 自动从默认仓库 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 模式详细流程

  1. 上次 ls-remote 校验在 1 小时内 → 直接信任缓存,不调用 git ls-remote
  2. 超过 1 小时 → 调用 git ls-remote <repo> <ref> 校验 commit hash
  3. hash 相同 → 更新 last_check_at,命中缓存
  4. hash 不同 → 上游已变化,使用 Git fetch/reset 更新仓库缓存,再处理包内容
  5. 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 路径(正斜杠序列列化)和对象名追加到清单,原子替换 EXE
  • uninstall --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.jsoninstall 命令会自动将其同步到当前用户的 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 Xtype 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 会自动处理:

  1. 二进制 PBL 文件(推荐用于分发):mylib.pbl 直接作为编译好的库安装
  2. 源码目录形式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.zippb9/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.jsondependencies 声明当前包依赖的其他包,安装时会递归处理:

{
  "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"
}

打包与上传流程

  1. 按上述结构组织本地文件夹(文件夹名即默认包名)
  2. 若有多版本制品,准备对应的 <pb>.zip 并在 pbnpm.json 中声明
  3. 上传到 pbpkg 仓库:

bat pbnpm upload D:\path\to\mylib --tag v20260811 --token <gitee_token>

  1. 上传后包会出现在仓库的 <首字母>/<包名>/ 目录(如 m/mylib
  2. 其他人即可通过 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"?

--pbinstall 必传参数,表示目标 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.exechcp 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,可改用本地安装绕过:

  1. 浏览器手动下载仓库 master 分支压缩包:

https://gitee.com/paulajam/pbpkg/repository/archive/master.zip

  1. 解压到本地任意目录,例如 D:\pbpkg-master,解压后目录结构应为:

D:\pbpkg-master\ ├── p\ # 各包目录(pbidea、pbmss、python34_full ...) ├── t\ # 模板目录(tpl_app ...) └── index.json

  1. 直接用本地路径替代包名/模板名执行命令:

```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.