方向反转:此前 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 重写为单源开发流程。
258 lines
7.8 KiB
Markdown
258 lines
7.8 KiB
Markdown
---
|
||
title: 修改代码 - 原理
|
||
created: 2026-05-18
|
||
tags:
|
||
- SAP
|
||
- ADT
|
||
- edit
|
||
- lock
|
||
- activation
|
||
- REST
|
||
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
|
||
prev: "[[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]"
|
||
next: "[[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]"
|
||
---
|
||
|
||
# 修改代码 — 原理
|
||
|
||
> [!abstract] 核心流程
|
||
> ADT 中修改代码遵循 **锁定 → 编辑 → 保存 → 解锁 → 激活** 的严格工作流。每一步都通过 REST API 调用对应的 ABAP 后端服务完成。
|
||
|
||
## 1. 完整工作流
|
||
|
||
```
|
||
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
||
│ 1.锁定 │───▶│ 2.编辑 │───▶│ 3.保存 │───▶│ 4.解锁 │───▶│ 5.激活 │
|
||
│ Lock │ │ Edit │ │ Save │ │ Unlock │ │ Activate│
|
||
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||
│ │ │ │ │
|
||
▼ ▼ ▼ ▼ ▼
|
||
POST lock GET source PUT source POST unlock POST activate
|
||
→ lockHandle → 纯文本 + lockHandle + lockHandle → 激活结果
|
||
```
|
||
|
||
> [!warning] 重要约束
|
||
> - **必须先锁定** 才能修改代码,否则 PUT 请求会被拒绝
|
||
> - **锁定句柄 (lockHandle)** 是贯穿整个编辑流程的关键凭证
|
||
> - **必须激活** 后代码才会在运行时生效(保存 ≠ 激活)
|
||
|
||
## 2. Step 1: 锁定对象 (Lock)
|
||
|
||
### 原理
|
||
|
||
ABAP 使用 **乐观锁 (Optimistic Lock)** 机制。当开发者打开一个对象进行编辑时,ADT 向后端发送锁定请求,获取一个唯一的 `lockHandle`。
|
||
|
||
### REST API 调用
|
||
|
||
```
|
||
POST /sap/bc/adt/oo/classes/{class_name}
|
||
?_action=LOCK
|
||
&accessMode=MODIFY
|
||
```
|
||
|
||
### 响应
|
||
|
||
```xml
|
||
<adtcore:object xmlns:adtcore="http://www.sap.com/adt/core"
|
||
LOCK_HANDLE="0123456789ABCDEF"/>
|
||
```
|
||
|
||
- 返回的 `LOCK_HANDLE` 是一个唯一标识符
|
||
- 后续的写入操作必须携带此句柄
|
||
- 锁定是 **会话级** 的,HTTP 会话断开后锁自动释放
|
||
|
||
### 后端处理
|
||
|
||
```
|
||
1. ADT Framework 接收锁定请求
|
||
↓
|
||
2. 调用 ABAP 锁定管理器 (ENQUEUE)
|
||
↓
|
||
3. 检查对象是否已被其他用户锁定
|
||
↓
|
||
4. 如果未锁定 → 创建锁定条目,返回 lockHandle
|
||
5. 如果已锁定 → 返回错误(锁定冲突)
|
||
```
|
||
|
||
### 对应 SAP GUI
|
||
|
||
| ADT 操作 | SAP GUI 等效 |
|
||
|----------|-------------|
|
||
| Lock 对象 | 打开 SE24/SE38 编辑模式时自动锁定 |
|
||
|
||
## 3. Step 2: 读取源代码 (Read Source)
|
||
|
||
### REST API 调用
|
||
|
||
```
|
||
GET /sap/bc/adt/oo/classes/{class_name}/source/main
|
||
Accept: text/plain
|
||
```
|
||
|
||
### 对象路径模式
|
||
|
||
不同对象类型使用不同的路径:
|
||
|
||
| 对象类型 | 路径模板 |
|
||
|----------|---------|
|
||
| 类 | `oo/classes/{name}/source/main` |
|
||
| 程序 | `programs/programs/{name}/source/main` |
|
||
| 函数模块 | `functions/groups/{group}/fmodules/{fm}/source/main` |
|
||
| CDS View | `ddic/cds/views/{name}/source/main` |
|
||
|
||
### 响应
|
||
|
||
直接返回 **纯文本** 格式的 ABAP 源代码(Content-Type: `text/plain`)。
|
||
|
||
## 4. Step 3: 写入源代码 (Write Source)
|
||
|
||
### REST API 调用
|
||
|
||
```
|
||
PUT /sap/bc/adt/oo/classes/{class_name}/source/main
|
||
Content-Type: text/plain; charset=utf-8
|
||
X-sap-adt-lockhandle: {lockHandle}
|
||
|
||
<修改后的 ABAP 源代码>
|
||
```
|
||
|
||
> [!important] 关键要点
|
||
> - 请求体是 **纯文本**(不是 XML)
|
||
> - **必须携带** `X-sap-adt-lockhandle` Header
|
||
> - 保存 ≠ 激活:代码已写入仓库但仍处于 **非活跃状态**
|
||
|
||
### 后端处理
|
||
|
||
```
|
||
1. 验证 lockHandle 是否有效
|
||
↓
|
||
2. 将源代码写入 ABAP Repository 的非活跃版本 (Inactive Version)
|
||
↓
|
||
3. 对象状态变为 "Modified"(在 Project Explorer 中显示 * 标记)
|
||
↓
|
||
4. 记录到传输请求任务中(如果已分配)
|
||
```
|
||
|
||
### 与 SAP GUI 的对比
|
||
|
||
| 特性 | SAP GUI (SE80/SE38) | ADT (Eclipse) |
|
||
|------|---------------------|---------------|
|
||
| 保存行为 | 保存即激活(或保存为非活跃) | 保存 ≠ 激活(显式分离) |
|
||
| 锁定管理 | 隐式(打开编辑时自动锁定) | 显式(REST API 锁定/解锁) |
|
||
| 版本管理 | 版本数据库 | 本地历史 + 版本数据库 |
|
||
|
||
## 5. Step 4: 解锁对象 (Unlock)
|
||
|
||
### REST API 调用
|
||
|
||
```
|
||
POST /sap/bc/adt/oo/classes/{class_name}
|
||
?_action=UNLOCK
|
||
&lockHandle={lockHandle}
|
||
```
|
||
|
||
解锁后,其他开发者可以锁定并编辑该对象。
|
||
|
||
## 6. Step 5: 激活对象 (Activate)
|
||
|
||
### 原理
|
||
|
||
> [!tip] 激活 vs 保存
|
||
> - **保存 (Save)**:将源代码写入仓库的非活跃版本
|
||
> - **激活 (Activate)**:编译代码并生成可执行版本(Active Version)
|
||
> - 只有激活后的代码才能被运行时系统使用
|
||
|
||
### REST API 调用
|
||
|
||
```
|
||
POST /sap/bc/adt/activation
|
||
Content-Type: application/xml
|
||
|
||
<?xml version="1.0" encoding="utf-8"?>
|
||
<adtcore:objectReferences
|
||
xmlns:adtcore="http://www.sap.com/adt/core">
|
||
<adtcore:objectReference
|
||
adtcore:name="{object_name}"
|
||
adtcore:uri="/sap/bc/adt/oo/classes/{class_name}"/>
|
||
</adtcore:objectReferences>
|
||
```
|
||
|
||
### 后端激活流程
|
||
|
||
```
|
||
1. ADT 激活框架接收激活请求
|
||
↓
|
||
2. 执行预检查 (Pre-audit)
|
||
├── 语法检查 (Syntax Check)
|
||
├── 依赖检查(引用的对象是否已激活)
|
||
└── 权限检查
|
||
↓
|
||
3. 如果预检查通过:
|
||
├── 编译 ABAP 代码 → 生成 ABAP Load
|
||
├── 更新 ABAP Repository 中的活跃版本 (Active Version)
|
||
├── 触发 ATC 检查(如果配置了自动检查)
|
||
└── 将活跃版本写入传输请求
|
||
↓
|
||
4. 如果预检查失败:
|
||
└── 返回错误列表(语法错误、缺失引用等)
|
||
```
|
||
|
||
### 对应 SAP GUI
|
||
|
||
| ADT 操作 | SAP GUI 等效 |
|
||
|----------|-------------|
|
||
| 激活单个对象 | `SE80` → Activate (Ctrl+F3) |
|
||
| 批量激活 | `SE80` → Inactive Objects → Activate All |
|
||
|
||
## 7. 完整 REST API 调用序列示例
|
||
|
||
以下是一个修改 ABAP 类的完整 REST API 调用序列:
|
||
|
||
```python
|
||
# 1. 锁定对象
|
||
lock_resp = POST("/sap/bc/adt/oo/classes/zcl_example",
|
||
_action="LOCK", accessMode="MODIFY")
|
||
lock_handle = parse_lock_handle(lock_resp)
|
||
|
||
# 2. 读取当前源代码
|
||
source = GET("/sap/bc/adt/oo/classes/zcl_example/source/main",
|
||
Accept="text/plain")
|
||
|
||
# 3. 修改源代码(客户端操作)
|
||
modified_source = modify_source(source)
|
||
|
||
# 4. 写入修改后的源代码
|
||
PUT("/sap/bc/adt/oo/classes/zcl_example/source/main",
|
||
body=modified_source,
|
||
headers={"X-sap-adt-lockhandle": lock_handle})
|
||
|
||
# 5. 解锁对象
|
||
POST("/sap/bc/adt/oo/classes/zcl_example",
|
||
_action="UNLOCK", lockHandle=lock_handle)
|
||
|
||
# 6. 激活对象
|
||
POST("/sap/bc/adt/activation",
|
||
body=activation_xml("ZCL_EXAMPLE",
|
||
"/sap/bc/adt/oo/classes/zcl_example"))
|
||
```
|
||
|
||
## 8. 错误处理
|
||
|
||
| 场景 | HTTP 状态码 | 处理方式 |
|
||
|------|-----------|---------|
|
||
| 对象已被锁定 | 403/409 | 提示用户等待或强制解锁 |
|
||
| CSRF Token 过期 | 403 | 自动重新获取 Token 并重试 |
|
||
| 语法错误 | 400 | 返回错误列表,阻止激活 |
|
||
| 权限不足 | 401/403 | 检查 S_DEVELOP 权限 |
|
||
|
||
## 🔗 相关笔记
|
||
|
||
- [[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]
|
||
- [[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]
|
||
|
||
## 📚 参考来源
|
||
|
||
- [SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide)
|
||
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
|
||
- [erpl-adt (GitHub)](https://github.com/DataZooDE/erpl-adt)
|