@shundoo-ai/dsh-cosmic 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/cosmic-build/scripts/kd_token_cache.py +1 -1
- package/skills/cosmic-build/skills/cosmic-vibe-coding/SKILL.md +248 -120
- package/skills/cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md +3 -3
- package/skills/erp-analysis/scripts/pull_project_templates.py +1 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
- package/skills/cosmic-build/skills/cosmic-vibe-coding/scripts/cosmic-hotdeploy.ps1 +0 -283
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Cosmic Assistant",
|
|
4
|
-
"description": "Cosmic
|
|
4
|
+
"description": "Cosmic Assistant: development and data analysis for Kingdee Cloud Cosmic ERP, covering form metadata modelling, plugin development with hot deploy, and data visualization reports."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shundoo-ai/dsh-cosmic",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "苍穹助手:金蝶云苍穹ERP的开发与数据分析,涵盖表单元数据建模、插件开发与热部署、数据可视化报告。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
7
7
|
"deepseek-harness",
|
|
@@ -53,7 +53,7 @@ def _get_env_name() -> str:
|
|
|
53
53
|
"""从工作区配置 .dsh/cosmic/.kd/config.json 读取 default=true 的环境名,失败返回 'default'。"""
|
|
54
54
|
try:
|
|
55
55
|
config_path = Path(os.getcwd()) / ".dsh" / "cosmic" / ".kd" / "config.json"
|
|
56
|
-
with open(config_path, "r", encoding="utf-8") as f:
|
|
56
|
+
with open(config_path, "r", encoding="utf-8-sig") as f:
|
|
57
57
|
config = json.load(f)
|
|
58
58
|
env_data = config.get("env", {})
|
|
59
59
|
if isinstance(env_data, dict):
|
|
@@ -10,8 +10,11 @@ description: >
|
|
|
10
10
|
| 职责 | 内容 | 载体 |
|
|
11
11
|
|------|------|------|
|
|
12
12
|
| ① 开启热部署 | 开 `jar_deploy_enable` 开关、连 IDEA MCP、生成 `dev.json` | `scripts/dev.py` + `assets/dev.json` |
|
|
13
|
-
| ② 热加载 | 不重启、不碰 mc,把 jar 注入运行中 cosmic | `assets/kd-reloadjar-agent.jar
|
|
14
|
-
| ③ build + 热部署 | 构建 jar(MCP 定位编译错误)→ 热加载 |
|
|
13
|
+
| ② 热加载 | 不重启、不碰 mc,把 jar 注入运行中 cosmic | `assets/kd-reloadjar-agent.jar`(**先用机器级缓存副本**,见 R2.3) |
|
|
14
|
+
| ③ build + 热部署 | 构建 jar(MCP 定位编译错误)→ 热加载 | gradle 命令行 + agent |
|
|
15
|
+
|
|
16
|
+
> 职责②③ 全程走下面的**运行手册(Runbook)**:决策规则写死、每步有检查点、失败有分支表。
|
|
17
|
+
> **macOS / Linux / Windows 共用同一份手册**,平台差异在步骤内标注(本 skill 不再提供平台专用封装脚本)。
|
|
15
18
|
|
|
16
19
|
## 触发场景
|
|
17
20
|
|
|
@@ -37,50 +40,16 @@ description: >
|
|
|
37
40
|
{VENV}/bin/python {SKILL_DIR}/scripts/dev.py read
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
- 输出的 JSON 里 `erp_url` / `account_id` / `java_home` 等**关键字段非空** →
|
|
43
|
+
- 输出的 JSON 里 `erp_url` / `account_id` / `java_home` 等**关键字段非空** → 配置就绪,直接走运行手册。
|
|
41
44
|
- 输出 `{}`(未初始化)→ 引导用户跑 `{VENV}/bin/python {SKILL_DIR}/scripts/dev.py init` 初始化(读 `.kd/config.json` 覆盖 dev.json),即职责①。
|
|
42
45
|
|
|
43
46
|
---
|
|
44
47
|
|
|
45
|
-
## Windows:一条命令走完全程(推荐)
|
|
46
|
-
|
|
47
|
-
> 下面各职责给的是 **macOS/Linux** 命令。Windows 上**手拼这套流程代价极高** —— 实测一个 Windows 会话为跑通「构建 + 热部署」发了 **63 条 pwsh**、**6 次提权**(探测 PID → 找 JDK → 找 tools.jar → attach,每步都要单独授权)。
|
|
48
|
-
|
|
49
|
-
本 skill 随包提供封装脚本,**把全程收敛成一次调用 ⇒ 一次授权就够**:
|
|
50
|
-
|
|
51
|
-
```powershell
|
|
52
|
-
# ⭐ 推荐:直接带 -Module 一次跑完(脚本内部会先体检,再构建 + 热部署)
|
|
53
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名>
|
|
54
|
-
# 干净重建 + 热部署
|
|
55
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名> -Clean
|
|
56
|
-
# ⭐ 真编译 + 热部署(禁构建缓存 + 强制重跑,保证 javac 真的跑)
|
|
57
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名> -Clean -NoCache
|
|
58
|
-
# 只热部署已有 jar
|
|
59
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Jar <jar路径>
|
|
60
|
-
# 只构建、不部署
|
|
61
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1 -Module <模块名> -WhatIf
|
|
62
|
-
# 仅体检(已经有授权时用;没授权时它会因端口查询被拒而报错,见下)
|
|
63
|
-
pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
> ⚠️ **本脚本在 Windows 上需要放开沙箱(`danger-full-access`),且必须「整条命令一次授权」**:
|
|
67
|
-
> 它要 `Get-NetTCPConnection` 查端口归属、并跨进程 attach 到 cosmic JVM,受限沙箱下二者都被拒
|
|
68
|
-
> (`CreatePipe error=5` / `access denied`)。
|
|
69
|
-
> **所以:不要先跑一次「体检」再去跑构建** —— 那会白花一次授权。直接带 `-Module` 发**一条**命令、**一次性**授权,全流程都在这一次调用里完成。
|
|
70
|
-
> 若端口查询被沙箱拒绝,脚本会**明确报出这一点**(而不是误报「cosmic 没在跑」)—— 看到该提示就带沙箱放开重发同一条命令。
|
|
71
|
-
|
|
72
|
-
内部依次完成:定位工作区 → 解析 preset bundle → 读 `dev.json` → **按端口反查 cosmic PID**(不是 `jps`,见职责②)→ 挑含 `tools.jar` 的 JDK → 构建 → Attacher 热加载。
|
|
73
|
-
|
|
74
|
-
参数:`-Module`|`-Jar`|`-Clean`|`-NoCache`|`-WhatIf`|`-LogFile`|`-Workspace`|`-PresetDir`|`-Port`。
|
|
75
|
-
工作区默认从「脚本位置的上两级」或当前目录推断;布局不同就传 `-Workspace <项目根>`。
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
48
|
## 职责①:开启热部署(每个环境一次性)
|
|
80
49
|
|
|
81
|
-
1. **IDEA 启动 MCP(可选)**:JetBrains MCP,固定 URL `http://127.0.0.1:64342/stream`,用于**代码智能**(符号查找、调用分析、重构、即时查错)。⚠️ **非构建必需**——产 jar 走 gradle
|
|
50
|
+
1. **IDEA 启动 MCP(可选)**:JetBrains MCP,固定 URL `http://127.0.0.1:64342/stream`,用于**代码智能**(符号查找、调用分析、重构、即时查错)。⚠️ **非构建必需**——产 jar 走 gradle 命令行(见 R3),MCP 未配/未连不影响「build + 热部署」主线。
|
|
82
51
|
|
|
83
|
-
**首次连接检查**:确认 DSH 已配置 IDEA MCP——读
|
|
52
|
+
**首次连接检查**:确认 DSH 已配置 IDEA MCP——读 **`$DSH_PROFILE_DIR/cordis.patch.yml`**(= `$DSH_HOME/profiles/<profile>/cordis.patch.yml`;**不要写死 profile 名**,本机是 `web`,别的机器可能不同),应有 `mcp-jetbrains` 条目。若无,写入:
|
|
84
53
|
|
|
85
54
|
```yaml
|
|
86
55
|
- insert:
|
|
@@ -92,16 +61,22 @@ pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1
|
|
|
92
61
|
url: http://127.0.0.1:64342/stream
|
|
93
62
|
```
|
|
94
63
|
|
|
95
|
-
>
|
|
64
|
+
> ✅ **该注册方式已对着最新已发布版本(`0.1.7-rc.1`,npm `next` 标签)核实无变化**:插件名 `@deepseek-ai/dsh-mcp-client`、patch 文件(`cordis.patch.yml`,顶层数组 + `- insert:`)、字段(`serverName` 必填且须匹配 `^[A-Za-z0-9_-]{1,32}$`、`transport: streamable-http` 常量、`url` 必填、`headers` 可选)全部一致;DSH 命令行**没有** `mcp add` 之类的子命令,注册只能走这个 patch 文件。另支持 `transport: stdio`(需 `command`/`args`),本项目用不到。
|
|
65
|
+
> 🔌 `url` 的**端口是 IDEA 侧**(JetBrains MCP Server 插件,默认 `64342`),不是 DSH 设置 —— 插件改过端口就同步改这里。
|
|
66
|
+
> 确认生效:`dsh --profile <profile> --dump-config | grep -A4 mcp-jetbrains`,或直接看 `mcp__jetbrains__*` 工具是否出现。
|
|
67
|
+
|
|
68
|
+
> 配置修改通常**立即生效**(本 profile `package.json` 里 `patchReload: live`,无需重启);若新增 `mcp-jetbrains` 条目后 `mcp__jetbrains__*` 工具仍未出现,需**新会话**或重启 DSH。
|
|
96
69
|
|
|
97
70
|
> ⚠️ **必传 `projectPath`**:调 IDEA MCP 工具(`build_project` / `get_project_modules` / `lint_files` / `search_symbol` / `analyze_calls` 等)时,**一律显式传 `projectPath=<cwd>`**(= 当前工作区根目录 = cwd),否则报 `Unable to determine the target project`。即使工具参数列表未列出该参数也要传(DSH 会透传),**不要用 `execute_tool` 包一层**。
|
|
98
71
|
2. **用 `scripts/dev.py` 生成环境配置**(从 `.kd/config.json` 读,不手工输入):
|
|
99
72
|
|
|
100
73
|
```bash
|
|
101
|
-
{VENV}/bin/python {SKILL_DIR}/scripts/dev.py init
|
|
102
|
-
{VENV}/bin/python {SKILL_DIR}/scripts/dev.py read
|
|
74
|
+
{VENV}/bin/python {SKILL_DIR}/scripts/dev.py init
|
|
75
|
+
{VENV}/bin/python {SKILL_DIR}/scripts/dev.py read
|
|
103
76
|
```
|
|
104
77
|
|
|
78
|
+
子命令:`init` = **首次**(读 `.kd/config.json` 的 url,覆盖 dev.json,并同步 `.gitignore`/ACP overlay);`read` = **日常只读**。两者都幂等,首次连着跑一遍即可(init 完再 read 验证)。
|
|
79
|
+
|
|
105
80
|
`dev.py init` 读 `<cwd>/.dsh/cosmic/.kd/config.json`(default env,复用 kd_env_loader,与 erp-analysis 同源),取 `url` 自动解析端口;**accountId 配置有值直接用,为空则调 `getAllDatacenters.do` 自动填**(优先 `default:true` 的数据中心,否则取第一个;失败保持空),**tenantId 配置有值用配置、为空默认 `ierp`**、`java_home` 从 IDEA 项目 SDK 自动发现,并**覆盖** `<cwd>/.dsh/cosmic/config/dev.json`。**若 url 为空,脚本中断并提醒「设置 erp 登录信息」。**
|
|
106
81
|
|
|
107
82
|
> `dev.py init` 同时按模板 `assets/gitignore.template`(Java/Gradle/IDE/Python/凭证/运行时产物完整段)确保 `<cwd>/.gitignore`:无则整体拷贝模板,有则逐条目幂等补缺(缺失项以「cosmic 开发/热部署」标记段追加,只增不删;模板后续新增条目也会补上)。
|
|
@@ -109,109 +84,207 @@ pwsh -File {SKILL_DIR}\scripts\cosmic-hotdeploy.ps1
|
|
|
109
84
|
|
|
110
85
|
---
|
|
111
86
|
|
|
112
|
-
##
|
|
87
|
+
## 运行手册(Runbook)|职责②③ 共用
|
|
113
88
|
|
|
114
|
-
|
|
89
|
+
> **只热加载已有 jar**:走 R0 → R1 → R2 → R4 → R5(跳过 R3)。
|
|
90
|
+
> **build + 热部署**:R0 → R1 → R2 → R3 → R4 → R5。
|
|
91
|
+
> 每一步都给「期望」与「失败处置」;**不做即兴发挥**,缺规则就回写手册(见文末自检)。
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
> **平台约定:本手册三平台通用**(Windows / macOS / Linux 各步骤都可用)。平台差异见下表;
|
|
94
|
+
> 失败分支表里**未标注平台的条目三平台通用**,标 `[Win]` 的只会发生在 Windows。
|
|
95
|
+
>
|
|
96
|
+
> 📋 **片段使用规则(照抄前先看这三条)**
|
|
97
|
+
> 1. `<...>` 是**占位符**,执行前必须替换成实际值(如 `<port>`、`<Gradle根>`、`<模块>`)。
|
|
98
|
+
> 2. 凡并列出现两段(bash / powershell),**按平台二选一,不要都执行**。
|
|
99
|
+
> 3. **配置类片段(yaml / json)保持纯净、注解一律在框外**——避免被原样写进真实配置文件;shell 片段里的 `#` 注释是 shell 语法的一部分,随片段一起拷贝无副作用。
|
|
100
|
+
>
|
|
101
|
+
> | 差异点 | Windows | macOS / Linux |
|
|
102
|
+
> |---|---|---|
|
|
103
|
+
> | PID 定位 | `Get-NetTCPConnection` → `Get-CimInstance`(**`jps` 看不到 SYSTEM 账户的 cosmic**) | `lsof -tiTCP:<port> -sTCP:LISTEN` 或 `jps -l \| grep JettyServer` |
|
|
104
|
+
> | classpath 分隔符 | `;` | `:` |
|
|
105
|
+
> | 沙箱 | 受限沙箱禁跨进程管道 → 端口查询 / attach 会被拒(`CreatePipe error=5`),需把命令合并后放开 | 无此限制 |
|
|
106
|
+
> | shell / 变量 | PowerShell(DSH 兜底到 5.1,注意默认 ANSI 解码);`$env:VAR=` | bash/sh;`export VAR=` |
|
|
107
|
+
> | 构建可执行 | `gradle.bat`(按 wrapper 定版) | `gradle`(按 wrapper 定版) |
|
|
108
|
+
> | 缓存预热命令 | `robocopy`(见 R3.2) | `cp -R`(见 R3.2) |
|
|
109
|
+
|
|
110
|
+
### R0 前置检查(任一失败即停)
|
|
111
|
+
|
|
112
|
+
| # | 检查 | 期望 | 失败处置 |
|
|
113
|
+
|---|------|------|----------|
|
|
114
|
+
| R0.1 | `<DSH工作区>/.dsh/cosmic/config/dev.json` 存在 | 存在 | 跑 `dev.py init`(或先「设置 erp 登录信息」) |
|
|
115
|
+
| R0.2 | dev.json 的 `port` / `account_id` / `tenant_id` | 非空 | `tenant_id` 空 → 默认 `ierp`;`account_id` 空 → 重跑 `dev.py init` |
|
|
116
|
+
| R0.3 | **HTTP 探测**端口(不依赖任何权限) | `200`/`302` | 非 2xx/3xx → cosmic 没起或端口不对,停 |
|
|
117
|
+
| R0.4 | 目标进程定位(R1.2) | 得到 PID,且命令行含 `kd.bos.service.webserver.JettyServer` | 见 F5 |
|
|
121
118
|
|
|
122
|
-
|
|
119
|
+
```bash
|
|
120
|
+
# R0.3 探测(Windows: Invoke-WebRequest http://127.0.0.1:<port>/ierp)
|
|
121
|
+
curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 http://127.0.0.1:<port>/ierp
|
|
122
|
+
```
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
- macOS 备选:`lsof -tiTCP:<port> -sTCP:LISTEN`
|
|
126
|
-
- **Windows 推荐(实测最可靠)**:按端口反查 + 命令行校验
|
|
124
|
+
> ⚠️ **顺序不可颠倒**:先 R0.3 再 R0.4。端口反查被沙箱拒绝 ≠ cosmic 没启动 —— 这两件事必须能区分开(Windows 上曾因 `jps` 为空误判"cosmic 没启动")。
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
$conn = Get-NetTCPConnection -LocalPort <port> -State Listen | Select-Object -First 1
|
|
130
|
-
(Get-CimInstance Win32_Process -Filter "ProcessId=$($conn.OwningProcess)").CommandLine # 应含 kd.bos.service.webserver.JettyServer
|
|
131
|
-
```
|
|
126
|
+
### R1 解析布局
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
> 另:发现 PID 后先扫命令行里有没有 `-XX:+DisableAttachMechanism` —— 带此参数的 JVM **无法 attach**(硬阻断)。
|
|
135
|
-
> 跨用户 attach 本身**可用、无需提权**(Windows JVM attach 命名管道对 Administrators 组放行)。
|
|
128
|
+
#### R1.1 两个"根"可以不同,必须分别解析
|
|
136
129
|
|
|
137
|
-
|
|
130
|
+
| 概念 | 判定规则 | 用途 |
|
|
131
|
+
|------|----------|------|
|
|
132
|
+
| **DSH 工作区** | 含 `.dsh/cosmic/.kd/config.json` 的目录 | 读凭证、`dev.json`、运行时产物 |
|
|
133
|
+
| **Gradle 工程根** | **从当前目录向上找到的第一个含 `settings.gradle` 的目录** | 构建、`.gradle/user-home`、jar 产物 |
|
|
134
|
+
|
|
135
|
+
> ⚠️ 实测存在两者不同的布局(Windows:工作区 `D:\...\dzjyy`、Gradle 根 `D:\...\dzjyy\cosmic`)。**不允许默认两者相同**:构建一律以 Gradle 根为准,`dev.json` 仍从 DSH 工作区读;若某工具只接受单一路径,传 Gradle 根并按需把 `.dsh` 打通(权宜,不是约定)。
|
|
136
|
+
|
|
137
|
+
#### R1.2 定位目标 cosmic 进程
|
|
138
|
+
|
|
139
|
+
**POSIX**
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
PID=$(lsof -tiTCP:<port> -sTCP:LISTEN | head -1) # 首选:按端口,天然唯一
|
|
143
|
+
# 备选 jps:本机常同时有 mc 与 cosmic 两个 JettyServer(实测 2 个),必须挑 appName=cosmic 的那个:
|
|
144
|
+
# jps -lvm | grep JettyServer | grep -- '-DappName=cosmic'
|
|
145
|
+
ps -o command= -p "$PID" | head -c 300 # 必须含 kd.bos.service.webserver.JettyServer
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Windows**
|
|
149
|
+
|
|
150
|
+
```powershell
|
|
151
|
+
$conn = Get-NetTCPConnection -LocalPort <port> -State Listen | Select-Object -First 1
|
|
152
|
+
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$($conn.OwningProcess)"
|
|
153
|
+
$proc.CommandLine # 必须含 kd.bos.service.webserver.JettyServer
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- ⚠️ **Windows 上 `jps` 看不到 cosmic**(实测):cosmic 常由 **SYSTEM** 账户启动,而 `jps` 只列举**当前用户**的 JVM → 输出为空,**极易误判「cosmic 没启动」**。用端口反查。
|
|
157
|
+
- 定位后必查命令行是否含 `-XX:+DisableAttachMechanism` —— 带此参数的 JVM **无法 attach**,直接停。
|
|
158
|
+
- 跨用户 attach **可用、无需提权**(Windows JVM attach 命名管道对 Administrators 组放行);但目标进程若是 **elevated(管理员启动)**,普通权限 Attacher 会 `拒绝访问`(见 F4)。
|
|
159
|
+
- 同机多个 cosmic 进程,认命令行区分(例:`mservice-cosmic\lib` + `-agentlib:jdwp=…address=5005` = 热部署目标;`mservice\lib` + `-Dmonitor.dev.model=debug` = 另一套)。**勿杀错进程。**
|
|
160
|
+
|
|
161
|
+
#### R1.3 定位模块目录与产物
|
|
162
|
+
|
|
163
|
+
1. 先解析 `settings.gradle` 里的 `project(':X').projectDir = new File('…')` 映射;
|
|
164
|
+
2. 没有映射 → **在 `code/` 下递归查找同名目录**(不要假设 `code\<模块>`:实测存在 `code\yun\<模块>` 这种多一层的情况);
|
|
165
|
+
3. 产物 = `<模块目录>/build/libs/*.jar`,排除 `*sources*` / `*javadoc*`,取最新。
|
|
166
|
+
|
|
167
|
+
### R2 准备:JDK / Gradle / agent jar
|
|
168
|
+
|
|
169
|
+
#### R2.1 JDK(Attacher 必须与目标进程**同大版本**,且是 JDK 不是 JRE)
|
|
170
|
+
|
|
171
|
+
选择顺序(取第一个满足判据者):
|
|
172
|
+
|
|
173
|
+
1. **目标进程自己的 JDK**(从进程可执行文件路径反推 `<jdk>/bin/java` → `<jdk>`)——最稳;
|
|
174
|
+
2. `$JAVA_HOME` / `%JAVA_HOME%`;
|
|
175
|
+
3. `dev.json` 的 `java_home`;
|
|
176
|
+
4. 常见安装根(Windows:`C:\Program Files\Java\*`、`D:\Java\*`、`%USERPROFILE%\.jdks`)。
|
|
177
|
+
|
|
178
|
+
判据:目录**含 `lib/tools.jar` 且含 `bin/java`**。找不到 → 停,报「需要 JDK8(含 tools.jar),不是 JRE」。
|
|
179
|
+
|
|
180
|
+
> ⚠️ 跨大版本 attach(JDK17 attach JDK8)会报 `AgentLoadException`。
|
|
181
|
+
> ⚠️ Windows 注册表 `HKLM\SOFTWARE\JavaSoft\Java Development Kit\JavaHome` 可能被 JRE 安装包改写 → Gradle 报 `Could not find tools.jar`;**必须显式设 `JAVA_HOME`**。
|
|
182
|
+
|
|
183
|
+
#### R2.2 Gradle(按 wrapper 锁定,不取"最新 dist")
|
|
138
184
|
|
|
139
|
-
> ⚠️ **别直接用 preset 里的 `{SKILL_DIR}/assets/kd-reloadjar-agent.jar`** —— 先把 agent jar 复制到**机器级缓存**再用(`{AGENT}`,见下)。原因:Attacher 会把这个 jar **载入运行中的 cosmic JVM**,而 JVM 的 classloader 会**长期持有**该文件句柄;preset 一旦升级,包管理器要**整目录替换**(每个文件都得能删/改名)→ 必然在这个 jar 上失败(Windows 报 `os error 32`/拒绝访问)。
|
|
140
|
-
>
|
|
141
|
-
> `{AGENT}` = `$DSH_HOME/cache/cosmic/agent/kd-reloadjar-agent-<sha1前8位>.jar`(**带内容哈希命名**,所以新版永远不会覆盖可能已被 JVM 载入的旧副本)。`init-python.{sh,ps1}` 会预先复制;`cosmic-hotdeploy.ps1` 也会自动解析并复制。手工操作时:
|
|
142
|
-
>
|
|
143
|
-
> **平台差异(重要)**
|
|
144
|
-
> - **Windows:必须**这么做。JVM 持有句柄会**直接让 preset 升级失败**(`os error 32`/拒绝访问),必须重启 cosmic 服务才能解开。
|
|
145
|
-
> - **macOS/Linux:不是必须**。POSIX 允许替换/删除**已被打开**的文件,直接用原件也安全(升级不会失败)。
|
|
146
|
-
> - **但仍然统一用副本**,理由有二:① 两个平台同一条规则,少一处"看平台下菜"的判断题;② 万一漏了复制这一步,在 macOS 上**无害**,在 Windows 上却会重新引入死锁 —— 统一规则对两种平台都安全。
|
|
147
|
-
>
|
|
148
185
|
```bash
|
|
149
|
-
#
|
|
186
|
+
# macOS / Linux
|
|
187
|
+
VER=$(grep -oE 'gradle-[0-9.]+-bin' "<Gradle根>/gradle/wrapper/gradle-wrapper.properties" | head -1 | sed 's/gradle-//;s/-bin//')
|
|
188
|
+
GRADLE=$(find ~/.gradle/wrapper/dists/gradle-$VER-bin -type f -path '*/bin/gradle' | head -1)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```powershell
|
|
192
|
+
# Windows
|
|
193
|
+
$VER = [regex]::Match((Get-Content "<Gradle根>\gradle\wrapper\gradle-wrapper.properties" -Raw -Encoding UTF8), 'gradle-([0-9.]+)-bin').Groups[1].Value
|
|
194
|
+
$GRADLE = Get-ChildItem "$env:USERPROFILE\.gradle\wrapper\dists\gradle-$VER-bin" -Recurse -Filter 'gradle.bat' -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
> ⚠️ 机器上可能并存要求 Java 22 的新版 Gradle —— 取"最新"会把构建带偏。
|
|
198
|
+
|
|
199
|
+
#### R2.3 agent jar(用机器级缓存副本,**不要**直接用 preset 原件)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# macOS / Linux
|
|
150
203
|
AGENT_DIR="${DSH_HOME:-$HOME/.dsh}/cache/cosmic/agent"
|
|
151
204
|
SRC="{SKILL_DIR}/assets/kd-reloadjar-agent.jar"
|
|
152
205
|
HASH=$( (shasum -a 1 "$SRC" 2>/dev/null || sha1sum "$SRC") | cut -c1-8 )
|
|
153
206
|
AGENT="$AGENT_DIR/kd-reloadjar-agent-$HASH.jar"
|
|
154
|
-
mkdir -p "$AGENT_DIR"
|
|
207
|
+
mkdir -p "$AGENT_DIR"; [ -f "$AGENT" ] || cp "$SRC" "$AGENT"
|
|
155
208
|
```
|
|
156
209
|
|
|
210
|
+
```powershell
|
|
211
|
+
# Windows
|
|
212
|
+
$DshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $HOME ".dsh" }
|
|
213
|
+
$AGENT_DIR = Join-Path $DshHome "cache\cosmic\agent"
|
|
214
|
+
$SRC = "{SKILL_DIR}\assets\kd-reloadjar-agent.jar"
|
|
215
|
+
$HASH = (Get-FileHash $SRC -Algorithm SHA1).Hash.Substring(0,8).ToLower()
|
|
216
|
+
$AGENT = Join-Path $AGENT_DIR "kd-reloadjar-agent-$HASH.jar"
|
|
217
|
+
New-Item -ItemType Directory -Force -Path $AGENT_DIR | Out-Null
|
|
218
|
+
if (-not (Test-Path $AGENT)) { Copy-Item $SRC $AGENT -Force }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- **Windows:必须**。Attacher 会把该 jar 载入运行中的 cosmic JVM,JVM 的 classloader **长期持有文件句柄** → preset 升级时包管理器要整目录替换(每个文件都得能删/改名)→ 必然在这个 jar 上失败(`os error 32`/拒绝访问),且必须重启 cosmic 才能解开。
|
|
222
|
+
- **macOS/Linux:不是必须**(POSIX 允许替换已被打开的文件),但**统一用副本**:少一处"看平台下菜"的判断,且万一漏做,在 macOS 上无害、在 Windows 上会重新引入死锁 —— 统一规则对两边都安全。
|
|
223
|
+
- 缓存名带**内容哈希**,所以新版 preset 永远不会覆盖可能已被 JVM 载入的旧副本。
|
|
224
|
+
|
|
225
|
+
### R3 构建(职责③;只热加载可跳过)
|
|
226
|
+
|
|
157
227
|
```bash
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<可选logFile绝对路径>
|
|
228
|
+
# macOS / Linux
|
|
229
|
+
export JAVA_HOME=<R2.1 选出的 JDK>
|
|
230
|
+
export GRADLE_USER_HOME=<Gradle根>/.gradle/user-home
|
|
231
|
+
cd <Gradle根> # ⚠️ gradle/gradlew 继承调用方 cwd
|
|
232
|
+
"$GRADLE" :<模块>:build -x test --console=plain
|
|
164
233
|
```
|
|
165
234
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
-
|
|
235
|
+
```powershell
|
|
236
|
+
# Windows
|
|
237
|
+
$env:JAVA_HOME = "<R2.1 选出的 JDK>"
|
|
238
|
+
$env:GRADLE_USER_HOME = "<Gradle根>\.gradle\user-home"
|
|
239
|
+
Push-Location "<Gradle根>" # ⚠️ gradle.bat 继承调用方 cwd
|
|
240
|
+
try { & $GRADLE ":<模块>:build" -x test --console=plain } finally { Pop-Location }
|
|
241
|
+
```
|
|
171
242
|
|
|
172
|
-
|
|
243
|
+
- 增量构建(内容感知:改几行代码约 5–10 秒,无改动约 1 秒)。
|
|
244
|
+
- 产物在 `<模块>/build/libs/*.jar`。
|
|
245
|
+
- ⚠️ 产 jar **只认 gradle 命令行**:IDEA MCP `build_project` 默认增量、源码未变会跳过(`isSuccess:true` 但 jar 不更新);勿用 `buildJar`/`deployJar`(那是冷启动拷贝到 lib/cus,热部署不需要)。
|
|
173
246
|
|
|
174
|
-
|
|
247
|
+
#### R3.1 「构建成功」≠「真的编译了」——必须区分
|
|
175
248
|
|
|
176
|
-
|
|
177
|
-
|
|
249
|
+
| 现象 | 含义 | 处置 |
|
|
250
|
+
|------|------|------|
|
|
251
|
+
| `:X:compileJava UP-TO-DATE` + 秒级完成 | **javac 没跑**,产物是旧字节码 | 要验证"改的代码进了 jar":`clean`(或 `--no-build-cache`)重跑,并核对 `build/classes` 的 class 数量/时间戳 |
|
|
252
|
+
| `:X:compileJava FROM-CACHE`(`clean` 后常见) | 从构建缓存还原,同样没跑 javac | 加 `--no-build-cache` |
|
|
253
|
+
| 有 javac 的 `deprecation` / `unchecked` 提示 | 真编译 | 正常 |
|
|
254
|
+
| `BUILD SUCCESSFUL` 但 `build/classes` 为空 | 伪成功 | 同上,重跑 |
|
|
178
255
|
|
|
179
|
-
`
|
|
256
|
+
> 真编译判据(三条同时看):① `compileJava` 后**没有** `UP-TO-DATE`/`FROM-CACHE`;② 出现 javac 的 `deprecation`/`unchecked`;③ `build/classes` 下 class 的 mtime 是**刚刚**。
|
|
257
|
+
> ⚠️ 本模板 `gradle.properties` 带 `org.gradle.caching=true`,`clean` 后仍可能 `FROM-CACHE`(macOS 实测 **879ms**、Windows 约 **2 秒** 跑完)。**要真编译必须加 `--no-build-cache`**(必要时再加 `--rerun-tasks` 强制重跑整任务图 —— 注意它在大模块上会连 jar transform 一起重跑,见 R3.2)。
|
|
180
258
|
|
|
181
|
-
|
|
259
|
+
#### R3.2 冷缓存:首次构建可能 20 分钟级(**先量再等**)
|
|
182
260
|
|
|
183
|
-
|
|
261
|
+
- 编译类路径 = `mservice-cosmic/lib/{bos,biz,trd,cus}`,实测可达 **4000+ jar / 1.9 GB**;Gradle 要逐类做 ASM 分析建增量编译快照。
|
|
262
|
+
- **判据**:`<Gradle根>/.gradle/user-home/caches/<ver>/javaCompile/classAnalysis.bin` **持续增长 = 正在建快照(正常)**;12 秒内该文件不涨**且**守护进程 CPU 增量 ≈ 0 = 可能真卡住。
|
|
263
|
+
- **加速**:从系统 Gradle 主目录搬现成快照(按内容哈希寻址,同机同 jar 可复用):
|
|
184
264
|
|
|
185
|
-
|
|
265
|
+
```powershell
|
|
266
|
+
# Windows
|
|
267
|
+
robocopy $env:USERPROFILE\.gradle\caches\<ver>\javaCompile <Gradle根>\.gradle\user-home\caches\<ver>\javaCompile /E
|
|
268
|
+
robocopy $env:USERPROFILE\.gradle\caches\jars-9 <Gradle根>\.gradle\user-home\caches\jars-9 /E
|
|
269
|
+
```
|
|
186
270
|
|
|
187
|
-
```bash
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# 真编译(验证工具链 / 排除缓存欺骗):加 --no-build-cache --rerun-tasks
|
|
193
|
-
GRADLE_USER_HOME=<cwd>/.gradle/user-home "$GRADLE" :<模块>:clean :<模块>:build -x test --no-build-cache --rerun-tasks
|
|
194
|
-
```
|
|
271
|
+
```bash
|
|
272
|
+
# macOS / Linux
|
|
273
|
+
cp -R ~/.gradle/caches/<ver>/javaCompile "<Gradle根>/.gradle/user-home/caches/<ver>/"
|
|
274
|
+
cp -R ~/.gradle/caches/jars-9 "<Gradle根>/.gradle/user-home/caches/"
|
|
275
|
+
```
|
|
195
276
|
|
|
196
|
-
|
|
197
|
-
- ⚠️ **`clean` 也不等于真编译**(平台中立):本模板 `gradle.properties` 带 `org.gradle.caching=true`,`clean` 后 `compileJava` 会直接 `FROM-CACHE` 还原 class(macOS 实测 **879ms**、Windows 实测约 **2 秒** 即跑完,javac 一次未调用)。**要真编译必须加 `--no-build-cache`**(对应 Windows 脚本的 `-NoCache`,逻辑同源)。
|
|
198
|
-
- ⚠️ **「伪成功构建」陷阱**(平台中立):`compileJava` 处于 `UP-TO-DATE` 时 Gradle **根本不加载编译器**,缺 tools.jar 也不报错 → 看到 `BUILD SUCCESSFUL` + jar 正常产出,**但里面是旧字节码**。首次接入务必用上面的真编译命令验证一次。
|
|
199
|
-
- **真编译判据(三条同时看)**:① Gradle 输出里 `compileJava` 后面**没有** `UP-TO-DATE` / `FROM-CACHE`;② stderr 出现 javac 的 `deprecation` / `unchecked` 提示;③ `build/classes` 下 class 的 mtime 是**刚刚**(旧字节码的特征是 class 时间戳停留在上一次真编译的时刻)。
|
|
200
|
-
- 产物在 `<模块>/build/libs/*.jar`(本环境 `code/yun/sd-khxty-yun/build/libs/sd-khxty-yun-1.0.0.jar`)。
|
|
201
|
-
- ⚠️ 产 jar **只认 gradle 命令行**:IDEA MCP `build_project` 默认增量、源码未变会跳过(`isSuccess:true` 但 jar 不更新);勿用 `buildJar`/`deployJar`(那是冷启动拷贝到 lib/cus,热部署不需要)。
|
|
277
|
+
实测:搬完全量真编译 **40s**、增量 **15s**(工作区 `.gradle` 约 1.0 GB)。
|
|
202
278
|
|
|
203
|
-
|
|
204
|
-
> - **必须显式设 `JAVA_HOME` 指向 JDK(不是 JRE)**:Windows 注册表 `HKLM\SOFTWARE\JavaSoft\Java Development Kit` 的 `JavaHome` 会被 JRE 安装包改写成 `...\jre1.8.0_481`,Gradle 采信它 → `java.home` 指向 JRE → 报 `Could not find tools.jar`。显式 `$env:JAVA_HOME="C:\Program Files\Java\jdk1.8.0_XXX"`
|
|
205
|
-
> - **Gradle 版本必须按 `gradle/wrapper/gradle-wrapper.properties` 锁定**,不要「取 `~/.gradle/wrapper/dists` 下最新」—— 机器上可能同时装了要求 Java 22 的新版 Gradle,会直接把构建带偏。
|
|
206
|
-
> - ⚠️ **缓存会掩盖工具链问题**:`UP-TO-DATE`(`clean` 后则直接 `FROM-CACHE`)时 Gradle 根本不加载编译器,缺 tools.jar 也不报错 → 首次接入务必用职责③的真编译命令验证一次;Windows 脚本对应 `-NoCache`。
|
|
207
|
-
>
|
|
208
|
-
> 完整分析见 `cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md` §4.3 / §4.7。Windows 上直接用上面「Windows:一条命令走完全程」的封装脚本即可,它已内置这些约束。
|
|
279
|
+
#### R3.3 长构建的执行方式(**别干等**)
|
|
209
280
|
|
|
210
|
-
|
|
281
|
+
1. **后台跑**,输出写日志文件;
|
|
282
|
+
2. **每 60 秒**读一次日志尾 + 用 R3.2 判据判断是否在推进;
|
|
283
|
+
3. 向用户汇报「当前阶段 / 已运行时长 / 下一判据」,不要沉默等到对方来问。
|
|
211
284
|
|
|
212
|
-
|
|
285
|
+
#### R3.4 定位编译错误 / 理解代码(IDEA MCP,可选)
|
|
213
286
|
|
|
214
|
-
|
|
287
|
+
> 可选:MCP 未配/未连不影响主线(gradle stderr 本身含文件名 + 行号)。
|
|
215
288
|
|
|
216
289
|
- `lint_files` / `get_file_problems`:查指定文件的编译错误/警告(定位到行)
|
|
217
290
|
- `search_symbol`:按类/方法名语义查找(比 grep 精确)
|
|
@@ -219,9 +292,63 @@ GRADLE_USER_HOME=<cwd>/.gradle/user-home "$GRADLE" :<模块>:clean :<模块>:bui
|
|
|
219
292
|
- `rename_refactoring`:安全重命名类/方法(全项目引用自动更新)
|
|
220
293
|
- `read_file`:读源码,含反编译 jar 里的类(如 `kd.bos.upgrade.ExtJarApi`)
|
|
221
294
|
|
|
222
|
-
###
|
|
295
|
+
### R4 注入(Attacher)
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
<jdk>/bin/java -cp "<jdk>/lib/tools.jar<sep><AGENT>" kd.reloadjar.Attacher \
|
|
299
|
+
<PID> "<AGENT>" \
|
|
300
|
+
"<jar绝对路径1>,<jar绝对路径2>" \
|
|
301
|
+
<account_id> <tenant_id> [<logFile绝对路径>]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- `<sep>`:macOS/Linux `:`,Windows `;`
|
|
305
|
+
- 多个 jar 用**逗号分隔**;agent 会 `save` 全部 + `reset` 一次
|
|
306
|
+
- jar 可直接用构建产物 `build/libs/*.jar`,**无需拷到 `lib/cus`**
|
|
307
|
+
- `logFile` 可选:填了则在开发机侧落成功/失败标记日志(`status=SUCCESS/FAILED`)
|
|
308
|
+
|
|
309
|
+
#### R4.1 attach 前反滑手清单(逐项确认后再执行)
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
[ ] AGENT 用的是机器级缓存副本(不是 preset 原件)
|
|
313
|
+
[ ] classpath 分隔符随平台(macOS/Linux ':'、Windows ';')
|
|
314
|
+
[ ] Attacher JDK 与目标进程同大版本,且 lib/tools.jar 存在
|
|
315
|
+
[ ] PID 的命令行含 kd.bos.service.webserver.JettyServer(没杀错进程)
|
|
316
|
+
[ ] jar 是绝对路径、且是本次构建产物(时间戳/大小对得上)
|
|
317
|
+
[ ] account_id / tenant_id 非空
|
|
318
|
+
[ ] 沙箱已放开(Windows 受限沙箱下跨进程 attach 会被拒)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### R5 验证
|
|
322
|
+
|
|
323
|
+
看 Attacher 命令的**退出码 + stdout**,以此为准:
|
|
324
|
+
|
|
325
|
+
- **成功**:退出码 `0`,stdout 打印 `SUCCESS: N jar(s) deployed & reloaded`(+ 标记日志 `status=SUCCESS`)。
|
|
326
|
+
- **失败**:退出码 `1`,stderr 打印 `FAILED`。
|
|
327
|
+
|
|
328
|
+
`vm.loadAgent()` 是同步等待 save+reset 完成的,正常返回即代表写表 + 重载都已成功。**一般不要查数据库。**
|
|
329
|
+
|
|
330
|
+
> 提示用户:热部署只影响当前运行会话(写 `t_meta_ext_jar`);**cosmic 重启后** `lib/cus` 由 `kd-appstore-download` 从 `apppackage-cosmic` 重拉、热部署内容失效 → 需重新 build + 热部署。
|
|
331
|
+
|
|
332
|
+
### 失败分支表(报错原文 → 含义 → 处置)
|
|
333
|
+
|
|
334
|
+
| # | 报错 / 现象 | 含义 | 处置 |
|
|
335
|
+
|---|-------------|------|------|
|
|
336
|
+
| F1 | `Unexpected UTF-8 BOM` | 凭证文件带 BOM,某处用裸 `utf-8` 读 | 去 BOM;**别再手编辑该 JSON,改走 `erp-config.py write`** |
|
|
337
|
+
| F2 | `does not contain a Gradle build` | 执行构建的 cwd 不是 Gradle 根 | 按 R1.1 切到含 `settings.gradle` 的目录 |
|
|
338
|
+
| F3 | `[Win]` `拒绝访问` / `CreatePipe error=5`(端口查询或 attach 阶段) | 沙箱未放开 | 把「体检 + 构建 + 注入」合成**尽量少的命令**并带 `danger-full-access` 重发;**不要先单独跑体检**(白花一次授权) |
|
|
339
|
+
| F4 | `[Win]` attach 报 `拒绝访问`(`WindowsVirtualMachine.openProcess`) | 目标进程是 elevated(管理员启动) | 提升 Attacher 权限执行(`Start-Process -Verb RunAs`);先确认目标是否 elevated |
|
|
340
|
+
| F5 | `jps` 查不到 / 端口反查为空 | ① Windows 上 cosmic 由 SYSTEM 启动 ② 端口错 ③ 沙箱拒绝 | 先用 R0.3 的 HTTP 探测区分"活着"与"查不到";活着 → 用 R1.2 端口反查 |
|
|
341
|
+
| F6 | `Could not find tools.jar` | `JAVA_HOME` 指向 JRE(或被注册表带偏) | 显式设 `JAVA_HOME` 到含 `lib/tools.jar` 的 JDK |
|
|
342
|
+
| F7 | `AgentLoadException` | Attacher 与目标 JVM 大版本不一致 | 用目标进程自己的 JDK(R2.1 第 1 顺位) |
|
|
343
|
+
| F8 | 构建长时间无输出 | 多半是冷缓存建快照,不一定是死锁 | 用 R3.2 判据;确卡住再清 Gradle 守护进程重试(**不要动 cosmic 进程**) |
|
|
344
|
+
| F9 | `Full recompilation is required because <类> could not be analyzed` | 增量编译失效(classpath 上有注解处理器 / Java17+ 类 / MR-JAR) | 见 `cosmic-dev-best-practices/references/cosmic-hotdeploy-build-slow-windows.md` §4 |
|
|
345
|
+
| F10 | 同一条命令第二次才成功(第一次报 cwd / 编码等) | 环境差异没被前移成决策规则 | **回写本手册**:补进 R1/R2 的规则或本表 |
|
|
346
|
+
|
|
347
|
+
### 自检(每轮执行后)
|
|
223
348
|
|
|
224
|
-
|
|
349
|
+
- **零即兴**:每一步都能在本手册里找到对应条目?
|
|
350
|
+
- **调用次数**:目标 **< 15 次**(散文时代 Windows 实测 63 次);超出 → 哪个环节缺规则 → 回写。
|
|
351
|
+
- **F10 事件**:同一命令跑了两次?→ 手册缺一条规则。
|
|
225
352
|
|
|
226
353
|
---
|
|
227
354
|
|
|
@@ -243,10 +370,10 @@ agent(`kd.reloadjar.ReloadAgent`)在 cosmic 进程内自动执行这两步
|
|
|
243
370
|
1. **lib/cus 不会被热监听**:`-classpath …/lib/cus/*` 只在启动时展开一次。
|
|
244
371
|
2. **reset 只读数据库,不读 lib/cus 目录**:所以必须先 `save` 写表。
|
|
245
372
|
3. **DB 访问要「托管线程」**:agent 已用 `ThreadPools` 提交(`BaseDB.checkThreadContext` 校验)。
|
|
246
|
-
4. **Attacher 必须和目标进程同 JDK
|
|
373
|
+
4. **Attacher 必须和目标进程同 JDK 大版本**(见 R2.1):跨版本 attach(如 JDK17 attach JDK8)会报 `AgentLoadException`。
|
|
247
374
|
5. **agent 类名 + jar 路径被 JVM 缓存**:改 agent 代码后要么重启 cosmic,要么换新类名 + 新 jar 名。
|
|
248
375
|
6. **accountId / tenantId 必填**:ExtClassLoader 按数据中心隔离,DB 访问要 RequestContext。
|
|
249
|
-
7. **构建**:日常用增量 `build`(Gradle 内容感知,删源文件也会清 stale class);要**真编译**(验证工具链、排除 `UP-TO-DATE` / `FROM-CACHE`
|
|
376
|
+
7. **构建**:日常用增量 `build`(Gradle 内容感知,删源文件也会清 stale class);要**真编译**(验证工具链、排除 `UP-TO-DATE` / `FROM-CACHE` 两类缓存欺骗)加 `--no-build-cache`(见 R3.1)。
|
|
250
377
|
8. **热部署 vs 冷启动**:热部署只影响当前运行会话(写 `t_meta_ext_jar`);重启后 `lib/cus` 由 `kd-appstore-download` 从 `apppackage-cosmic` 重新拉取。
|
|
251
378
|
|
|
252
379
|
---
|
|
@@ -257,5 +384,6 @@ agent(`kd.reloadjar.ReloadAgent`)在 cosmic 进程内自动执行这两步
|
|
|
257
384
|
|------|------|------|
|
|
258
385
|
| `assets/dev.json` | ① 开启热部署 | 空配置模板(`dev.py` 读它作字段基础,生成工作区 `.dsh/cosmic/config/dev.json`) |
|
|
259
386
|
| `scripts/dev.py` | ① 开启热部署 | 环境配置脚本(`init`=读 `.kd/config.json` 覆盖 dev.json;`read`=只读;`__file__` 反推 skill 根) |
|
|
260
|
-
| `
|
|
261
|
-
| `assets/
|
|
387
|
+
| `assets/kd-reloadjar-agent.jar` | ② 热加载 | 热重载 agent(含 `kd.reloadjar.ReloadAgent` + `Attacher`)。⚠️ **原件不要直接加载** —— 先复制到 `$DSH_HOME/cache/cosmic/agent/kd-reloadjar-agent-<sha1前8位>.jar` 再用(见 R2.3) |
|
|
388
|
+
| `assets/gitignore.template` | ① 开启热部署 | `<cwd>/.gitignore` 的权威模板(`dev.py init` 无则拷贝、有则幂等补缺,只增不删) |
|
|
389
|
+
| `assets/acp.cordis.patch.yml` | ① 开启热部署 | ACP 工作区 overlay 模板(`dev.py init` 覆盖到 `<cwd>/.dsh/profiles/acp/cordis.patch.yml`) |
|
|
@@ -226,9 +226,9 @@ $gradle = "C:\Users\<user>\.gradle\wrapper\dists\gradle-7.6.3-bin\<hash>\gradle-
|
|
|
226
226
|
> `~/.gradle/wrapper/dists` 下最新」。本机同时存在 `gradle-8.8-bin`,它要求 Java 22
|
|
227
227
|
> 运行时,与本项目 JDK 8 + tools.jar 链路不兼容,会直接把构建带偏。
|
|
228
228
|
>
|
|
229
|
-
> 🧩
|
|
230
|
-
>
|
|
231
|
-
>
|
|
229
|
+
> 🧩 完整流程(端口反查 PID / JDK 选择 / Gradle 定版 / 构建 / 注入)见
|
|
230
|
+
> `cosmic-vibe-coding/SKILL.md` 的**运行手册(R0–R5)**,其中已内置上述 JDK、Gradle 版本、
|
|
231
|
+
> PID 发现、account/tenant 读取逻辑,以及失败分支表(F1–F10)。
|
|
232
232
|
|
|
233
233
|
> ❌ 不要加 `--no-daemon`:每次 fork 单次 Daemon(JVM 冷启动 + 重新加载配置 ≈ +30s)
|
|
234
234
|
> ✅ 复用常驻 Daemon(`org.gradle.daemon=true` 默认开启),改几行代码约 5-10 秒
|
|
@@ -75,7 +75,7 @@ def main():
|
|
|
75
75
|
print("[tenant] 无 .dsh/cosmic/.kd/config.json,跳过拉取", file=sys.stderr)
|
|
76
76
|
return 0
|
|
77
77
|
try:
|
|
78
|
-
kd = json.loads(kd_path.read_text(encoding="utf-8"))
|
|
78
|
+
kd = json.loads(kd_path.read_text(encoding="utf-8-sig"))
|
|
79
79
|
except (OSError, json.JSONDecodeError) as e:
|
|
80
80
|
print(f"[tenant] .dsh/cosmic/.kd/config.json 读取失败: {e}", file=sys.stderr)
|
|
81
81
|
return 1
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
| GRADLE_USER_HOME | `<cwd>/.gradle/user-home` | gradle wrapper 缓存;dist 从系统 `~/.gradle/wrapper/dists` **复制**(勿用软链——wrapper 会删链重解压) |
|
|
42
42
|
| gradlew / gradlew.bat | POSIX:项目根 `gradlew` 需 `chmod +x`;Windows:需 `gradle/wrapper/`(gradle-wrapper.properties + gradle-wrapper.jar)齐全,`gradlew.bat` 才可运行 | 模板生成时可能缺 x 位;缺 wrapper 元数据时从模板/系统 dist 补齐 |
|
|
43
43
|
| 插件编译脚本 | `{VENV}/bin/python compile_plugin_source.py` | 脚本只注入 `JAVA_HOME`、**不注入 `GRADLE_USER_HOME`**,调用方必须显式 `export GRADLE_USER_HOME=<cwd>/.gradle/user-home`,否则 Gradle 默认写用户主目录(Windows:`%USERPROFILE%\.gradle`)触发权限拦截;cosmic_home、解释器约定见上 |
|
|
44
|
-
| 构建权限边界 | 产 jar 走 `:<模块>:build`(写工作区);勿用 `buildJar`/`deployJar`(写 cosmic 安装目录 outputdir,工作区外) | Windows 另有 `CreatePipe`
|
|
44
|
+
| 构建权限边界 | 产 jar 走 `:<模块>:build`(写工作区);勿用 `buildJar`/`deployJar`(写 cosmic 安装目录 outputdir,工作区外) | Windows 另有 `CreatePipe` 限制,构建/热部署须放开权限:受限沙箱下**把「体检+构建+注入」合成尽量少的命令**(授权按命令计),**不要先单独跑一次「体检」**(白花一次授权);完整步骤见 cosmic-vibe-coding 技能的**运行手册(R0–R5)**;Attacher classpath 分隔符 Windows 用 `;` |
|
|
45
45
|
|
|
46
46
|
> 📌 配置按**工作区**隔离(appconfig / GRADLE_USER_HOME / ERP 凭证均在 `<cwd>` 下),多工作区可并行开展不同项目;bash 每次调用为独立 shell,命令中需显式携带工作区约定值(如 `export GRADLE_USER_HOME=<cwd>/.gradle/user-home`),勿依赖跨调用 export。沙箱写权限仅限工作区(preset 技能脚本 `~/.dsh/...`、`~/kd` 等外部路径修改需授权)。权限放开按平台:**macOS/Linux 无管道限制,构建/热部署无需放开**(缓存入工作区即可);**Windows 禁跨进程管道(`CreatePipe error=5`),构建/热部署每次须单命令级放开 `danger-full-access`**(或会话级放宽;`--no-daemon` 无效)。
|
|
47
47
|
> 📌 **发邮件不需要提权**:`send_email.py` 用 `password = "授权码"` 时明文直读、**不起子进程**,纯网络调用,任何平台都无需放开权限。(只有 `auth_cmd` 那条路要起子进程捕获 stdout,Windows 受限令牌下才需提权 —— 见 `references/email-delivery.md`。)
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
# cosmic-hotdeploy.ps1 -- Kingdee Cosmic build + hot-deploy (Windows, portable)
|
|
2
|
-
#
|
|
3
|
-
# PURPOSE
|
|
4
|
-
# Windows port of the cosmic-vibe-coding skill workflow (the skill docs give
|
|
5
|
-
# macOS/Linux commands). Discovers the running cosmic JVM, builds the module
|
|
6
|
-
# with the wrapper-pinned Gradle + a real JDK8, then hot-loads the jar via the
|
|
7
|
-
# Attacher agent -- no cosmic restart, no touching mc.
|
|
8
|
-
#
|
|
9
|
-
# USAGE
|
|
10
|
-
# pwsh -File cosmic-hotdeploy.ps1 # health check only
|
|
11
|
-
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> # build + hot-deploy
|
|
12
|
-
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> -Clean # clean build + hot-deploy
|
|
13
|
-
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> -Clean -NoCache # REAL javac rebuild (no cache, no up-to-date)
|
|
14
|
-
# pwsh -File cosmic-hotdeploy.ps1 -Jar a.jar,b.jar # hot-deploy existing jars
|
|
15
|
-
# pwsh -File cosmic-hotdeploy.ps1 -Module <name> -WhatIf # dry run (build only)
|
|
16
|
-
#
|
|
17
|
-
# PORTABILITY
|
|
18
|
-
# The workspace is inferred from this script's location (expects to live in
|
|
19
|
-
# <workspace>/runtime/); override with -Workspace. The skill bundle is located
|
|
20
|
-
# via $DSH_PROFILE_DIR or $DSH_HOME; override with -PresetDir. No other
|
|
21
|
-
# machine-specific paths are hard-coded -- the JDK is discovered from the
|
|
22
|
-
# running cosmic process, then from JAVA_HOME, then from common install roots.
|
|
23
|
-
#
|
|
24
|
-
# NOTE
|
|
25
|
-
# Windows classpath separator is ';' (not ':'). The Attacher must run on a JDK
|
|
26
|
-
# whose major version matches the target JVM (JDK8 + lib\tools.jar for cosmic).
|
|
27
|
-
|
|
28
|
-
[CmdletBinding()]
|
|
29
|
-
param(
|
|
30
|
-
[string[]]$Jar,
|
|
31
|
-
[string]$Module,
|
|
32
|
-
[switch]$Clean,
|
|
33
|
-
[switch]$NoCache,
|
|
34
|
-
[switch]$WhatIf,
|
|
35
|
-
[string]$LogFile,
|
|
36
|
-
[string]$Workspace,
|
|
37
|
-
[string]$PresetDir,
|
|
38
|
-
[int]$Port
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
$ErrorActionPreference = 'Stop'
|
|
42
|
-
|
|
43
|
-
# ---- 0. Resolve workspace (no hard-coded user paths) ----
|
|
44
|
-
if (-not $Workspace) {
|
|
45
|
-
# Expect this script at <workspace>\runtime\cosmic-hotdeploy.ps1
|
|
46
|
-
if ($PSScriptRoot) {
|
|
47
|
-
$Workspace = Split-Path $PSScriptRoot -Parent
|
|
48
|
-
} else {
|
|
49
|
-
$Workspace = (Get-Location).Path
|
|
50
|
-
}
|
|
51
|
-
# If it was moved elsewhere, fall back to the cwd when that looks like a workspace.
|
|
52
|
-
if (-not (Test-Path (Join-Path $Workspace 'settings.gradle'))) {
|
|
53
|
-
if (Test-Path (Join-Path (Get-Location).Path 'settings.gradle')) {
|
|
54
|
-
$Workspace = (Get-Location).Path
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (-not (Test-Path (Join-Path $Workspace 'settings.gradle'))) {
|
|
59
|
-
throw "Workspace '$Workspace' has no settings.gradle. Pass -Workspace <project root>."
|
|
60
|
-
}
|
|
61
|
-
$cwd = (Resolve-Path $Workspace).Path
|
|
62
|
-
|
|
63
|
-
# ---- 0.1 Resolve the cosmic skill bundle ----
|
|
64
|
-
if (-not $PresetDir) {
|
|
65
|
-
$roots = @()
|
|
66
|
-
if ($env:DSH_PROFILE_DIR) { $roots += (Join-Path $env:DSH_PROFILE_DIR 'node_modules\@shundoo-ai\dsh-cosmic') }
|
|
67
|
-
if ($env:DSH_HOME) { $roots += (Join-Path $env:DSH_HOME '.agent-presets\cosmic') }
|
|
68
|
-
$PresetDir = $roots | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
|
|
69
|
-
}
|
|
70
|
-
if (-not $PresetDir) {
|
|
71
|
-
throw "Cannot locate the cosmic skill bundle. Set DSH_PROFILE_DIR/DSH_HOME, or pass -PresetDir <path containing skills\cosmic-build>."
|
|
72
|
-
}
|
|
73
|
-
$srcAgent = Join-Path $PresetDir 'skills\cosmic-build\skills\cosmic-vibe-coding\assets\kd-reloadjar-agent.jar'
|
|
74
|
-
if (-not (Test-Path $srcAgent)) { throw "Missing reload agent: $srcAgent" }
|
|
75
|
-
|
|
76
|
-
# ---- 0.2 Agent jar: use a machine-level, content-hashed COPY -- never load it from the preset ----
|
|
77
|
-
# Why: the Attacher loads this jar into the running cosmic JVM, and the JVM's classloader keeps
|
|
78
|
-
# the JarFile handle open for the JVM's lifetime. npm/pnpm upgrades replace the *whole package
|
|
79
|
-
# directory* (move the old dir away, put the new one in), which requires every file inside to be
|
|
80
|
-
# deletable/renamable -> the upgrade always fails on this jar (Windows: os error 32 / access
|
|
81
|
-
# denied). The old git-based layout never hit this because git only rewrites files whose content
|
|
82
|
-
# actually changed, and this jar rarely changes.
|
|
83
|
-
# Naming the copy with the content hash means a *new* preset version never overwrites an old copy
|
|
84
|
-
# that may already be loaded by a JVM.
|
|
85
|
-
$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME }
|
|
86
|
-
elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dsh' }
|
|
87
|
-
elseif ($env:HOME) { Join-Path $env:HOME '.dsh' }
|
|
88
|
-
else { $null }
|
|
89
|
-
$agent = $srcAgent
|
|
90
|
-
if ($dshHome) {
|
|
91
|
-
$agentHash = (Get-FileHash $srcAgent -Algorithm SHA1).Hash.Substring(0, 8).ToLower()
|
|
92
|
-
$agentDir = Join-Path (Join-Path $dshHome 'cache\cosmic') 'agent'
|
|
93
|
-
$cachedAgent = Join-Path $agentDir "kd-reloadjar-agent-$agentHash.jar"
|
|
94
|
-
try {
|
|
95
|
-
if (-not (Test-Path $cachedAgent)) {
|
|
96
|
-
New-Item -ItemType Directory -Force -Path $agentDir | Out-Null
|
|
97
|
-
Copy-Item $srcAgent $cachedAgent -Force
|
|
98
|
-
}
|
|
99
|
-
$agent = $cachedAgent
|
|
100
|
-
# best-effort housekeeping: keep only the newest few hashed copies
|
|
101
|
-
Get-ChildItem $agentDir -Filter 'kd-reloadjar-agent-*.jar' -ErrorAction SilentlyContinue |
|
|
102
|
-
Sort-Object LastWriteTime -Descending | Select-Object -Skip 5 |
|
|
103
|
-
Remove-Item -Force -ErrorAction SilentlyContinue
|
|
104
|
-
} catch {
|
|
105
|
-
Write-Host "[agent] WARN cannot use the cached copy ($($_.Exception.Message)); falling back to the preset copy (preset upgrades may then hit a file lock)."
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
$devScript = Join-Path $PresetDir 'skills\cosmic-build\skills\cosmic-vibe-coding\scripts\dev.py'
|
|
109
|
-
|
|
110
|
-
# ---- 1. Read dev.json (env config produced by dev.py init) ----
|
|
111
|
-
$devJson = Join-Path $cwd '.dsh\cosmic\config\dev.json'
|
|
112
|
-
if (-not (Test-Path $devJson)) {
|
|
113
|
-
throw "Missing $devJson -- run 'dev.py init' first (enable hot-deploy / set ERP login)."
|
|
114
|
-
}
|
|
115
|
-
$dev = Get-Content $devJson -Raw | ConvertFrom-Json
|
|
116
|
-
|
|
117
|
-
# Port: explicit -Port wins, then dev.json, then the port embedded in erp_url.
|
|
118
|
-
$effPort = if ($Port) { $Port }
|
|
119
|
-
elseif ($dev.port) { [int]$dev.port }
|
|
120
|
-
elseif ($dev.erp_url -match '^https?://[^:/]+:(\d+)') { [int]$Matches[1] }
|
|
121
|
-
else { 80 }
|
|
122
|
-
$accountId = $dev.account_id
|
|
123
|
-
$tenantId = $dev.tenant_id
|
|
124
|
-
if (-not $accountId) { throw "dev.json has no account_id -- re-run 'dev.py init'." }
|
|
125
|
-
|
|
126
|
-
# ---- 2. Discover the cosmic PID by port ownership, then validate the JVM ----
|
|
127
|
-
# NOTE: do NOT use -ErrorAction SilentlyContinue here. Get-NetTCPConnection is a cross-process
|
|
128
|
-
# query and the DSH sandbox DENIES it ("access denied") unless the call runs with danger-full-access.
|
|
129
|
-
# Swallowing that error makes an empty result mean two very different things, and the old code
|
|
130
|
-
# reported it as "cosmic is not running" -- sending the caller off to hunt a non-existent problem.
|
|
131
|
-
$conn = $null
|
|
132
|
-
try {
|
|
133
|
-
$conn = Get-NetTCPConnection -LocalPort $effPort -State Listen -ErrorAction Stop |
|
|
134
|
-
Select-Object -First 1
|
|
135
|
-
} catch {
|
|
136
|
-
throw @"
|
|
137
|
-
Port query was DENIED: $($_.Exception.Message)
|
|
138
|
-
This script needs the DSH sandbox opened for the whole run (Windows: danger-full-access).
|
|
139
|
-
Rationale: it must query port ownership (Get-NetTCPConnection) and attach to the cosmic JVM
|
|
140
|
-
across processes; both are blocked by the restricted sandbox (CreatePipe error=5 / access denied).
|
|
141
|
-
Re-run this single command with the sandbox escalated -- the whole discover+build+attach flow
|
|
142
|
-
is one call, so one grant covers it.
|
|
143
|
-
"@
|
|
144
|
-
}
|
|
145
|
-
if (-not $conn) { throw "Nothing is listening on port $effPort -- is cosmic running? (port query itself succeeded)" }
|
|
146
|
-
$targetPid = $conn.OwningProcess
|
|
147
|
-
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$targetPid"
|
|
148
|
-
if ($proc.CommandLine -notmatch 'kd\.bos\.service\.webserver\.JettyServer') {
|
|
149
|
-
throw "Process $targetPid on port $effPort is not the cosmic JettyServer: $($proc.CommandLine)"
|
|
150
|
-
}
|
|
151
|
-
if ($proc.CommandLine -match 'DisableAttachMechanism') {
|
|
152
|
-
throw "Process $targetPid has -XX:+DisableAttachMechanism -- hot-deploy cannot attach to it."
|
|
153
|
-
}
|
|
154
|
-
$owner = ($proc | Invoke-CimMethod -MethodName GetOwner).User
|
|
155
|
-
|
|
156
|
-
# ---- 3. Pick the JDK: prefer the one the cosmic process itself runs on ----
|
|
157
|
-
# Derive JAVA_HOME from the process executable (reliable, unlike parsing the
|
|
158
|
-
# command line, which may quote "C:\path with spaces"\bin\java differently).
|
|
159
|
-
$candidates = @()
|
|
160
|
-
if ($proc.ExecutablePath) {
|
|
161
|
-
$javaBin = Split-Path $proc.ExecutablePath -Parent # ...\jdk\bin
|
|
162
|
-
if ((Split-Path $javaBin -Leaf) -ieq 'bin') { $candidates += (Split-Path $javaBin -Parent) }
|
|
163
|
-
}
|
|
164
|
-
if ($env:JAVA_HOME) { $candidates += $env:JAVA_HOME }
|
|
165
|
-
# Common JDK install roots across drives (best effort, machine independent).
|
|
166
|
-
foreach ($root in @("$env:ProgramFiles\Java", "${env:ProgramFiles(x86)}\Java", 'C:\Java',
|
|
167
|
-
'D:\Java', 'D:\Program Files\Java', "$env:USERPROFILE\.jdks")) {
|
|
168
|
-
if ($root -and (Test-Path $root)) {
|
|
169
|
-
$candidates += (Get-ChildItem $root -Directory -ErrorAction SilentlyContinue |
|
|
170
|
-
Where-Object { $_.Name -match '^jdk' } | ForEach-Object { $_.FullName })
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
$candidates = $candidates | Where-Object { $_ } | Select-Object -Unique
|
|
174
|
-
|
|
175
|
-
# A usable JDK for the Attacher has lib\tools.jar (JDK8) and bin\java.exe.
|
|
176
|
-
$attacherJdk = $candidates |
|
|
177
|
-
Where-Object { (Test-Path (Join-Path $_ 'lib\tools.jar')) -and (Test-Path (Join-Path $_ 'bin\java.exe')) } |
|
|
178
|
-
Select-Object -First 1
|
|
179
|
-
if (-not $attacherJdk) {
|
|
180
|
-
throw @"
|
|
181
|
-
No usable JDK for the Attacher (needs lib\tools.jar + bin\java.exe).
|
|
182
|
-
Candidates tried: $($candidates -join ' | ')
|
|
183
|
-
The target cosmic JVM needs a matching JDK whose major version matches it.
|
|
184
|
-
If cosmic runs on JDK8, install a JDK8 (not a JRE!) and pass -Workspace/-PresetDir as needed,
|
|
185
|
-
or set `$env:JAVA_HOME to a real JDK.
|
|
186
|
-
"@
|
|
187
|
-
}
|
|
188
|
-
$java = Join-Path $attacherJdk 'bin\java.exe'
|
|
189
|
-
$toolsJar = Join-Path $attacherJdk 'lib\tools.jar'
|
|
190
|
-
|
|
191
|
-
Write-Host "[env] workspace = $cwd"
|
|
192
|
-
Write-Host "[env] erp_url = $($dev.erp_url) (port $effPort)"
|
|
193
|
-
Write-Host "[env] cosmic PID = $targetPid (owner=$owner)"
|
|
194
|
-
Write-Host "[env] attacher JDK = $attacherJdk"
|
|
195
|
-
Write-Host "[env] accountId = $accountId / tenantId = $tenantId"
|
|
196
|
-
if ($agent -ne $srcAgent) {
|
|
197
|
-
Write-Host "[agent] $agent (cached copy; the preset original is never loaded)"
|
|
198
|
-
} else {
|
|
199
|
-
Write-Host "[agent] $agent (PRESET ORIGINAL -- preset upgrades may hit a file lock while cosmic runs)"
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
# ---- 4. Optional: build first ----
|
|
203
|
-
if ($Module) {
|
|
204
|
-
$gradleHome = Join-Path $cwd '.gradle\user-home'
|
|
205
|
-
|
|
206
|
-
# 4.1 Gradle version must follow the wrapper, never "newest dist" -- a newer
|
|
207
|
-
# dist (e.g. 8.x) may demand a newer Java runtime and derail this JDK8 chain.
|
|
208
|
-
$wrapperProps = Join-Path $cwd 'gradle\wrapper\gradle-wrapper.properties'
|
|
209
|
-
if (-not (Test-Path $wrapperProps)) { throw "Missing $wrapperProps." }
|
|
210
|
-
$m = [regex]::Match((Get-Content $wrapperProps -Raw), 'gradle-([0-9.]+)-bin')
|
|
211
|
-
if (-not $m.Success) { throw "Cannot parse the Gradle version from $wrapperProps." }
|
|
212
|
-
$distVer = $m.Groups[1].Value
|
|
213
|
-
$distRoot = Join-Path $env:USERPROFILE ".gradle\wrapper\dists\gradle-$distVer-bin"
|
|
214
|
-
$gradleBat = Get-ChildItem $distRoot -Recurse -Filter 'gradle.bat' -ErrorAction SilentlyContinue |
|
|
215
|
-
Select-Object -First 1
|
|
216
|
-
if (-not $gradleBat) {
|
|
217
|
-
throw "Gradle $distVer not found under $distRoot. Let the wrapper download it first (run .\gradlew.bat)."
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
# 4.2 Build JDK: same preference order as above, but must carry tools.jar.
|
|
221
|
-
$buildJdk = $attacherJdk
|
|
222
|
-
if ($WhatIf) { Write-Host '[whatif] dry run: skipping the actual build.' }
|
|
223
|
-
|
|
224
|
-
$gradleArgs = @()
|
|
225
|
-
if ($Clean) { $gradleArgs += (':' + $Module + ':clean') }
|
|
226
|
-
$gradleArgs += (':' + $Module + ':build')
|
|
227
|
-
$gradleArgs += @('-x', 'test', '--console=plain')
|
|
228
|
-
# -NoCache: '--no-build-cache' stops the local build cache from restoring compileJava FROM-CACHE
|
|
229
|
-
# (this template's gradle.properties sets org.gradle.caching=true, so a plain -Clean rebuild can
|
|
230
|
-
# finish in ~2s without ever invoking javac), and '--rerun-tasks' defeats UP-TO-DATE, so javac
|
|
231
|
-
# really runs even when -Clean is not given.
|
|
232
|
-
if ($NoCache) { $gradleArgs += @('--no-build-cache', '--rerun-tasks') }
|
|
233
|
-
Write-Host "[build] gradle $distVer | $($gradleArgs -join ' ')"
|
|
234
|
-
if ($Clean -and -not $NoCache) {
|
|
235
|
-
Write-Host '[build] NOTE: -Clean alone can still restore classes FROM-CACHE (org.gradle.caching=true). Add -NoCache for a real javac run.'
|
|
236
|
-
}
|
|
237
|
-
Write-Host "[build] JAVA_HOME=$buildJdk (GRADLE_USER_HOME=$gradleHome)"
|
|
238
|
-
$env:GRADLE_USER_HOME = $gradleHome
|
|
239
|
-
$env:JAVA_HOME = $buildJdk
|
|
240
|
-
& $gradleBat.FullName @gradleArgs
|
|
241
|
-
if ($LASTEXITCODE -ne 0) { throw "Gradle build failed (exit $LASTEXITCODE) -- see the compile errors above." }
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
# ---- 5. Resolve jars (auto-locate the build output when -Module was given) ----
|
|
245
|
-
if ((-not $Jar -or $Jar.Count -eq 0) -and $Module) {
|
|
246
|
-
$settings = Get-Content (Join-Path $cwd 'settings.gradle') -Raw
|
|
247
|
-
$mm = [regex]::Match($settings,
|
|
248
|
-
"project\(':$([regex]::Escape($Module))'\)\.projectDir\s*=\s*new File\('([^']+)'\)")
|
|
249
|
-
$moduleDir = if ($mm.Success) { Join-Path $cwd ($mm.Groups[1].Value -replace '/', '\') }
|
|
250
|
-
else { Join-Path $cwd "code\$Module" }
|
|
251
|
-
$libsDir = Join-Path $moduleDir 'build\libs'
|
|
252
|
-
$Jar = @(Get-ChildItem $libsDir -Filter '*.jar' -ErrorAction SilentlyContinue |
|
|
253
|
-
Where-Object { $_.Name -notlike '*sources*' -and $_.Name -notlike '*javadoc*' } |
|
|
254
|
-
Sort-Object LastWriteTime -Descending | Select-Object -First 1 |
|
|
255
|
-
ForEach-Object { $_.FullName })
|
|
256
|
-
if (-not $Jar) { throw "No build output jar in $libsDir." }
|
|
257
|
-
}
|
|
258
|
-
if (-not $Jar -or $Jar.Count -eq 0) {
|
|
259
|
-
Write-Host '[done] Health check only -- no hot-deploy (pass -Jar or -Module).'
|
|
260
|
-
exit 0
|
|
261
|
-
}
|
|
262
|
-
$jars = $Jar | ForEach-Object { (Resolve-Path $_).Path }
|
|
263
|
-
foreach ($j in $jars) {
|
|
264
|
-
Write-Host "[jar] $j ($([math]::Round((Get-Item $j).Length / 1KB)) KB)"
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
# ---- 6. Attach: save the jar bytes, then reload the ExtClassLoader ----
|
|
268
|
-
$attArgs = @(
|
|
269
|
-
'-cp', "$toolsJar;$agent",
|
|
270
|
-
'kd.reloadjar.Attacher',
|
|
271
|
-
$targetPid, $agent,
|
|
272
|
-
($jars -join ','),
|
|
273
|
-
$accountId, $tenantId
|
|
274
|
-
)
|
|
275
|
-
if ($LogFile) {
|
|
276
|
-
$dir = Split-Path $LogFile -Parent
|
|
277
|
-
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
278
|
-
$attArgs += (Join-Path (Resolve-Path $dir).Path (Split-Path $LogFile -Leaf))
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
& $java @attArgs
|
|
282
|
-
if ($LASTEXITCODE -ne 0) { throw "Hot-deploy failed (exit $LASTEXITCODE)." }
|
|
283
|
-
Write-Host '[done] Hot-deploy complete (SUCCESS = save + reset both applied).'
|