方向反转:此前 SKILL.md 是「模板渲染产物」、sap-cli 是源;现 sap-cli 归档, sap-cli-skill 承接开发与分发,SKILL.md 回归手工维护的正本。 迁移(来自 sap-cli,共 104 文件): - tests/ 692 例测试(15 个文件的内联 sys.path 改指 assets/) - openspec/ SDD 规格与归档变更(42 文件) - docs/ 开发文档与 ADT 原理(含 dev/CLAUDE.md、AGENTS.md) - .claude/ rules 副本 + settings.json(供 Claude Code) - .github/ .hermes/ .pre-commit-config.yaml .editorconfig CLAUDE.md - scripts/ 保持仅 setup.py(pack_skill.py 已随旧仓归档,不迁) 修复(迁移暴露的真实缺陷): - assets/pyproject.toml 的 build-backend 写作 `setuptools.backends._legacy:_Backend`, 该模块在 setuptools 中不存在 → `pip install -e` 从来装不上。改为 build_meta。 实测:临时 venv 安装成功,sap-cli --help 正常列出 31 个命令 - pyproject readme 指向不存在的 assets/README.md(editable 安装会失败)→ 改内联文本 - pyproject urls 改指 sap-cli-skill 机制调整: - .github/workflows/ci.yml 适配 assets/ 布局;顶部注明该工作流仅 GitHub 执行, 本仓在 Gitee 不会自动跑 - pre-commit 增本地测试门禁(Gitee 上真正生效的那道) - .gitignore 合并旧仓完整规则(保留 log/ 下 md 知识库入库,只忽略运行日志) - 大文件上限 100KB→1MB(架构图 512KB) 守卫测试 tests/unit/test_repo_guards.py(10 → 18 例): - SKILL.md 须记录 parser 全部 CLI 命令 / 铁律 1-5 须为真实小节标题 / 示例不得违反铁律 5 - references/ 规则齐备;.claude/rules 与 references 必须一致(实测抓到一次真实漂移) - VERSION == sapcli.__version__ == README 版本 - 仓内不得再出现 pack_skill.py / skill-src(防废弃流程回潮) 698 tests OK;editable 安装与 CLI 入口经临时 venv 实测通过。 docs/RELEASING.md 重写为单源开发流程。
10 KiB
title, created, tags, parent, prev
| title | created | tags | parent | prev | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ADT 代码同步工具 — 使用指南 | 2026-05-19 |
|
sap-cli/README | sap-cli/06.ADT测试代码 |
ADT 代码同步工具 — 使用指南
[!abstract] 概述 通过
test_adt_client.py脚本实现本地与 SAP 系统之间的 ABAP 代码双向同步。支持拉取代码到本地编辑、修改后推送回 SAP 并自动激活。完整工作流遵循 sap-cli/03.修改代码-原理 中描述的 锁定 → 编辑 → 保存 → 解锁 → 激活 流程。
1. 快速开始
前置条件
pip install requests
基本用法
# 拉取代码(从 SAP 下载到本地)
python sap-cli/test_adt_client.py pull
# 推送代码(从本地上传到 SAP 并激活)
python sap-cli/test_adt_client.py push
配置修改
脚本顶部的配置区域:
ADT_BASE_URL = "http://support.learningleader.com.cn:55955" # SAP 服务器地址
SAP_CLIENT = "100" # SAP 客户端
USER = "admin2" # 用户名
PASSWORD = "654321" # 密码
PROGRAM_NAME = "ZIDTR_IMPORT_ACCOUNTING_ORDER" # 程序名
2. 拉取代码 (Pull)
流程
登录 → GET 源代码 → 保存到本地 .abap 文件
REST API 调用序列
| 步骤 | HTTP 方法 | URL | 说明 |
|---|---|---|---|
| 登录 | GET |
/sap/bc/adt/compatibility/graph |
获取 CSRF Token |
| 读取源码 | GET |
/sap/bc/adt/programs/programs/{name}/source/main |
Accept: text/plain |
关键请求头
GET /sap/bc/adt/programs/programs/zidtr_import_accounting_order/source/main
Accept: text/plain
x-csrf-token: {token}
X-sap-adt-sessiontype: stateless
sap-client: 100
输出
源代码保存到脚本同目录下的 {程序名小写}.abap 文件,例如:
sap-cli/zidtr_import_accounting_order.abap
[!tip] 格式说明 SAP ADT API 返回的源代码每行之间可能插入多余空行。脚本已内置格式优化逻辑,自动清理连续空行。
3. 推送代码 (Push)
完整流程
登录 → 查询传输请求 → 锁定对象 → 写入源代码 → 解锁对象 → 激活对象
对应 sap-cli/03.修改代码-原理 中的完整工作流:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 1.锁定 │───▶│ 2.写入 │───▶│ 3.解锁 │───▶│ 4.激活 │
│ Lock │ │ Write │ │ Unlock │ │ Activate│
└─────────┘ └─────────┘ └─────────┘ └─────────┘
REST API 调用序列
Step 0: 登录获取 CSRF Token
GET /sap/bc/adt/compatibility/graph
x-csrf-token: fetch
X-sap-adt-sessiontype: stateless
响应头中返回 CSRF Token,后续所有写操作必须携带。
Step 1: 查询传输请求
GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+xml
响应 XML 中解析第一个状态为 D(可修改)的传输请求号。
响应示例:
<tm:request tm:number="DEVK901360" tm:status="D" tm:desc="更改记录" />
[!important] 传输请求号 (corrNr) 传输请求号是代码写入 SAP 的必要参数。脚本自动查询并使用第一个可修改的请求。
XML 属性名带有命名空间前缀:
{http://www.sap.com/cts/adt/tm}number
Step 2: 锁定对象
POST /sap/bc/adt/programs/programs/{name}
?_action=LOCK
&accessMode=MODIFY
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
响应:
<adtcore:object LOCK_HANDLE="Jn333u2TRIqrbxSAs9ib7Ket9q0=" />
[!warning] 会话状态 锁定操作必须在 stateful 会话中执行(
X-sap-adt-sessiontype: stateful)。 登录端点必须使用/sap/bc/adt/compatibility/graph(不是/discovery),否则锁定会失败。
Step 3: 写入源代码
PUT /sap/bc/adt/programs/programs/{name}/source/main
?lockHandle={lockHandle}
&corrNr={传输请求号}
Content-Type: text/plain; charset=utf-8
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
{ABAP 源代码内容}
[!important] 关键参数
- lockHandle:来自锁定步骤的返回值,作为查询参数传递
- corrNr:传输请求号,也是查询参数
- 请求体是纯文本(不是 XML),UTF-8 编码
Step 4: 解锁对象
POST /sap/bc/adt/programs/programs/{name}
?_action=UNLOCK
&lockHandle={lockHandle}
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
Step 5: 激活对象
POST /sap/bc/adt/activation
?method=activate
&preauditRequested=true
Content-Type: application/xml
x-csrf-token: {token}
<?xml version="1.0" encoding="UTF-8"?>
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
<adtcore:objectReference
adtcore:uri="/sap/bc/adt/programs/programs/{name}"
adtcore:name="{PROGRAM_NAME}"/>
</adtcore:objectReferences>
激活响应可能包含错误消息:
<chkl:messages>
<msg type="E" line="1">
<shortText><txt>Type "XXX" is unknown.</txt></shortText>
</msg>
</chkl:messages>
| type | 含义 |
|---|---|
E |
错误 — 阻止激活 |
W |
警告 — 不阻止 |
I |
信息 — 不阻止 |
S |
成功 |
[!note] 空响应 激活成功时可能返回空响应体(HTTP 200),这表示激活无错误。
4. 传输请求详解
查询可用的传输请求
GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+xml
响应结构
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<tm:request xmlns:tm="http://www.sap.com/cts/adt/tm"
tm:number="DEVK901360"
tm:status="D"
tm:owner="ADMIN2"
tm:desc="更改记录的已生成请求"/>
<tm:task tm:number="DEVK901361" tm:status="D" tm:owner="ADMIN2"/>
</entry>
</feed>
属性说明
| 属性 | 值 | 说明 |
|---|---|---|
status |
D |
可修改 (Modifiable) |
status |
R |
已发布 (Released) |
number |
DEVK901360 |
传输请求号 |
desc |
文本描述 | 请求描述 |
XML 解析注意事项
属性名带有命名空间前缀,解析时需要使用完整名称:
tm_ns = "http://www.sap.com/cts/adt/tm"
for req in root.findall(f".//{{{tm_ns}}}request"):
number = req.attrib.get(f"{{{tm_ns}}}number", "") # 不是 "number"
status = req.attrib.get(f"{{{tm_ns}}}status", "") # 不是 "status"
5. 日志系统
每次执行 push/pull 操作时,详细的 API 请求/响应日志会写入:
sap-cli/adt_client.log
日志内容
2026-05-19 10:57:56 | INFO | REQUEST: POST http://.../activation
2026-05-19 10:57:56 | INFO | PARAMS: {'method': 'activate', 'preauditRequested': 'true'}
2026-05-19 10:57:56 | INFO | HEADER: x-csrf-token = xxx...
2026-05-19 10:57:56 | INFO | BODY: <?xml version="1.0" ...>
2026-05-19 10:57:56 | INFO | RESPONSE [ACTIVATE]: HTTP 200 OK
2026-05-19 10:57:56 | INFO | BODY (xml): <chkl:messages>...
2026-05-19 10:57:56 | INFO | Activation msg [E] line=1: Type "XXX" is unknown.
2026-05-19 10:57:56 | INFO | Activation summary: 1 errors, 0 warnings, 0 info
2026-05-19 10:57:56 | WARNING | Activation has ERRORS - object may NOT be active!
排查激活失败
- 查看
adt_client.log中[ACTIVATE]部分 - 搜索
Activation msg [E]找到错误消息 - 错误消息中的
line是语句序号(不是源文件行号) href中的start=XXX是字符偏移量,可用于定位源码位置
6. 完整 API 端点速查
通用端点
| 操作 | 方法 | 端点 | 关键参数 |
|---|---|---|---|
| 登录/CSRF | GET |
/sap/bc/adt/compatibility/graph |
x-csrf-token: fetch |
| 服务发现 | GET |
/sap/bc/adt/discovery |
— |
| 激活 | POST |
/sap/bc/adt/activation |
method=activate |
程序 (PROG) 端点
| 操作 | 方法 | 端点 | 关键参数 |
|---|---|---|---|
| 读取源码 | GET |
/sap/bc/adt/programs/programs/{name}/source/main |
Accept: text/plain |
| 锁定 | POST |
/sap/bc/adt/programs/programs/{name} |
_action=LOCK |
| 写入源码 | PUT |
/sap/bc/adt/programs/programs/{name}/source/main |
lockHandle, corrNr |
| 解锁 | POST |
/sap/bc/adt/programs/programs/{name} |
_action=UNLOCK |
类 (CLASS) 端点
| 操作 | 方法 | 端点 |
|---|---|---|
| 读取源码 | GET |
/sap/bc/adt/oo/classes/{name}/source/main |
| 锁定 | POST |
/sap/bc/adt/oo/classes/{name} |
| 写入源码 | PUT |
/sap/bc/adt/oo/classes/{name}/source/main |
| 解锁 | POST |
/sap/bc/adt/oo/classes/{name} |
CTS 传输管理端点
| 操作 | 方法 | 端点 |
|---|---|---|
| 查询传输请求 | GET |
/sap/bc/adt/cts/transportrequests |
| 创建传输请求 | POST |
/sap/bc/adt/cts/transportrequests |
| 发布传输请求 | POST |
/sap/bc/adt/cts/transportrequests/{number}/newreleasejobs |
7. ABAP 开发对象激活步骤
[!important] 标准激活流程
- 修改本地源代码 — 在
.abap文件中编辑- 同步到 SAP —
python test_adt_client.py push
- 脚本自动执行:锁定 → 写入 → 解锁 → 激活
- 查看日志确认 — 检查
adt_client.log确认无激活错误
🔗 相关笔记
- sap-cli/03.修改代码-原理 — 锁定/编辑/激活原理
- sap-cli/04.检查代码-原理 — 语法检查与 ATC
- sap-cli/05.提交与传输-原理 — CTS 传输管理
- sap-cli/06.ADT测试代码 — 初始测试脚本