Skip to content

Commit eaebd1d

Browse files
committed
<chore>[devtool]: add SDK and ApiHelper generators to dev-tool
Resolves: ZSTAC-0 Change-Id: I77ed018901e69b3073e3b6ece77ae4867173e166
1 parent 9a25e32 commit eaebd1d

File tree

3 files changed

+525
-6
lines changed

3 files changed

+525
-6
lines changed

zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import org.zstack.devtool.checker.ApiHelperChecker;
44
import org.zstack.devtool.checker.GlobalConfigDocChecker;
55
import org.zstack.devtool.checker.SdkChecker;
6+
import org.zstack.devtool.generator.ApiHelperGenerator;
67
import org.zstack.devtool.generator.GlobalConfigDocGenerator;
8+
import org.zstack.devtool.generator.SdkGenerator;
79
import org.zstack.devtool.model.ApiMessageInfo;
810
import org.zstack.devtool.model.GlobalConfigInfo;
911
import org.zstack.devtool.scanner.ApiMessageScanner;
@@ -89,12 +91,12 @@ public void generate(String target) {
8991
generateGlobalConfig();
9092
}
9193

92-
// SDK and ApiHelper generation requires compilation (use ./runMavenProfile)
93-
if ("sdk".equals(target)) {
94-
System.out.println("[SDK] Generate not supported yet. Run: ./runMavenProfile sdk");
94+
if ("all".equals(target) || "sdk".equals(target)) {
95+
generateSdk();
9596
}
96-
if ("apihelper".equals(target)) {
97-
System.out.println("[ApiHelper] Generate not supported yet. Run: ./runMavenProfile apihelper");
97+
98+
if ("all".equals(target) || "apihelper".equals(target)) {
99+
generateApiHelper();
98100
}
99101
}
100102

@@ -158,6 +160,20 @@ private List<GlobalConfigInfo> scanAllGlobalConfigs() {
158160

159161
// --- SDK ---
160162

163+
private void generateSdk() {
164+
List<ApiMessageInfo> messages = getApiMessages();
165+
if (messages.isEmpty()) {
166+
System.out.println("[SDK] WARN - no API messages found.");
167+
return;
168+
}
169+
170+
Path sdkDir = projectRoot.resolve("sdk/src/main/java/org/zstack/sdk");
171+
SdkGenerator generator = new SdkGenerator();
172+
int created = generator.generate(messages, sdkDir, true);
173+
System.out.println("[SDK] Generated " + created + " new file(s), " +
174+
messages.size() + " total API messages");
175+
}
176+
161177
private boolean checkSdk() {
162178
List<ApiMessageInfo> messages = getApiMessages();
163179
if (messages.isEmpty()) {
@@ -200,6 +216,25 @@ private boolean checkApiHelper() {
200216
return result.passed();
201217
}
202218

219+
private void generateApiHelper() {
220+
List<ApiMessageInfo> messages = getApiMessages();
221+
if (messages.isEmpty()) {
222+
System.out.println("[ApiHelper] WARN - no API messages found.");
223+
return;
224+
}
225+
226+
Path apiHelperFile = projectRoot.resolve(
227+
"testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy");
228+
if (!Files.exists(apiHelperFile)) {
229+
apiHelperFile = projectRoot.resolve(
230+
"premium/test-premium/src/main/groovy/org/zstack/testlib/ApiHelper.groovy");
231+
}
232+
233+
ApiHelperGenerator generator = new ApiHelperGenerator();
234+
int added = generator.generate(messages, apiHelperFile);
235+
System.out.println("[ApiHelper] Added " + added + " new method(s)");
236+
}
237+
203238
// --- API message scanning (shared by SDK + ApiHelper) ---
204239

205240
private List<ApiMessageInfo> getApiMessages() {
@@ -311,7 +346,7 @@ static void printUsage() {
311346
System.out.println();
312347
System.out.println("Commands:");
313348
System.out.println(" check [globalconfig|sdk|apihelper|all] Check if generated files are up to date");
314-
System.out.println(" generate [globalconfig|all] Generate missing files");
349+
System.out.println(" generate [globalconfig|sdk|apihelper|all] Generate missing files");
315350
System.out.println(" scan [globalconfig|sdk|apihelper|all] List all scanned items (debug)");
316351
System.out.println();
317352
System.out.println("Examples:");
@@ -320,5 +355,8 @@ static void printUsage() {
320355
System.out.println(" dev-tool check sdk Check SDK action files only");
321356
System.out.println(" dev-tool check apihelper Check ApiHelper.groovy methods");
322357
System.out.println(" dev-tool generate globalconfig Generate missing GlobalConfig docs");
358+
System.out.println(" dev-tool generate sdk Generate missing SDK action/result files");
359+
System.out.println(" dev-tool generate apihelper Generate missing ApiHelper.groovy methods");
360+
System.out.println(" dev-tool generate all Generate all missing files");
323361
}
324362
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package org.zstack.devtool.generator;
2+
3+
import org.zstack.devtool.model.ApiMessageInfo;
4+
5+
import java.io.IOException;
6+
import java.nio.charset.StandardCharsets;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.nio.file.StandardOpenOption;
10+
import java.util.ArrayList;
11+
import java.util.HashSet;
12+
import java.util.List;
13+
import java.util.Set;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
16+
17+
public class ApiHelperGenerator {
18+
19+
public int generate(List<ApiMessageInfo> messages, Path apiHelperFile) {
20+
if (!Files.exists(apiHelperFile)) {
21+
System.out.println("[ApiHelper] WARN - ApiHelper.groovy not found at " + apiHelperFile);
22+
return 0;
23+
}
24+
25+
String content;
26+
try {
27+
content = new String(Files.readAllBytes(apiHelperFile), StandardCharsets.UTF_8);
28+
} catch (IOException e) {
29+
System.err.println("[ApiHelper] ERROR - Cannot read " + apiHelperFile + ": " + e.getMessage());
30+
return 0;
31+
}
32+
33+
// Extract existing method names
34+
Set<String> existingMethods = new HashSet<>();
35+
Pattern pattern = Pattern.compile("def\\s+(\\w+)\\s*\\(");
36+
Matcher matcher = pattern.matcher(content);
37+
while (matcher.find()) {
38+
existingMethods.add(matcher.group(1));
39+
}
40+
41+
// Collect missing methods
42+
List<ApiMessageInfo> missing = new ArrayList<>();
43+
for (ApiMessageInfo msg : messages) {
44+
if (!existingMethods.contains(msg.getHelperMethodName())) {
45+
missing.add(msg);
46+
}
47+
}
48+
49+
if (missing.isEmpty()) {
50+
return 0;
51+
}
52+
53+
// Find insertion point: just before the closing brace of the class
54+
// ApiHelper.groovy ends with a closing "}" on its own line
55+
int insertionPoint = findInsertionPoint(content);
56+
if (insertionPoint < 0) {
57+
System.err.println("[ApiHelper] ERROR - Cannot find insertion point in " + apiHelperFile);
58+
return 0;
59+
}
60+
61+
StringBuilder newMethods = new StringBuilder();
62+
for (ApiMessageInfo msg : missing) {
63+
newMethods.append(generateMethod(msg));
64+
}
65+
66+
String newContent = content.substring(0, insertionPoint)
67+
+ newMethods
68+
+ content.substring(insertionPoint);
69+
70+
try {
71+
Files.write(apiHelperFile, newContent.getBytes(StandardCharsets.UTF_8),
72+
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
73+
System.out.println("[ApiHelper] Added " + missing.size() + " method(s)");
74+
for (ApiMessageInfo msg : missing) {
75+
System.out.println(" Added: " + msg.getHelperMethodName());
76+
}
77+
} catch (IOException e) {
78+
System.err.println("[ApiHelper] ERROR - Cannot write " + apiHelperFile + ": " + e.getMessage());
79+
return 0;
80+
}
81+
82+
return missing.size();
83+
}
84+
85+
private int findInsertionPoint(String content) {
86+
// Find the last closing brace which ends the class body
87+
int lastBrace = content.lastIndexOf('}');
88+
if (lastBrace < 0) return -1;
89+
// Walk back to find a newline before it
90+
int lineStart = content.lastIndexOf('\n', lastBrace);
91+
if (lineStart < 0) lineStart = 0;
92+
// Return the position of that newline (insert before last closing brace line)
93+
return lineStart + 1;
94+
}
95+
96+
private String generateMethod(ApiMessageInfo msg) {
97+
String methodName = msg.getHelperMethodName();
98+
String actionFqn = "org.zstack.sdk." + msg.getActionName();
99+
100+
StringBuilder sb = new StringBuilder();
101+
sb.append("\n\n");
102+
sb.append(" def ").append(methodName)
103+
.append("(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = ")
104+
.append(actionFqn).append(".class) Closure c) {\n");
105+
sb.append(" def a = new ").append(actionFqn).append("()\n");
106+
sb.append(" a.sessionId = Test.currentEnvSpec?.session?.uuid\n");
107+
sb.append(" c.resolveStrategy = Closure.OWNER_FIRST\n");
108+
sb.append(" c.delegate = a\n");
109+
sb.append(" c()\n");
110+
sb.append(" \n");
111+
112+
// Query actions need conditions coercion
113+
if (msg.isQuery()) {
114+
sb.append(" a.conditions = a.conditions.collect { it.toString() }\n");
115+
}
116+
117+
sb.append("\n\n");
118+
sb.append(" if (System.getProperty(\"apipath\") != null) {\n");
119+
sb.append(" if (a.apiId == null) {\n");
120+
sb.append(" a.apiId = Platform.uuid\n");
121+
sb.append(" }\n");
122+
sb.append(" \n");
123+
sb.append(" def tracker = new ApiPathTracker(a.apiId)\n");
124+
sb.append(" def out = errorOut(a.call())\n");
125+
sb.append(" def path = tracker.getApiPath()\n");
126+
sb.append(" if (!path.isEmpty()) {\n");
127+
sb.append(" Test.apiPaths[a.class.name] = path.join(\" --->\\n\")\n");
128+
sb.append(" }\n");
129+
sb.append(" \n");
130+
sb.append(" return out\n");
131+
sb.append(" } else {\n");
132+
sb.append(" return errorOut(a.call())\n");
133+
sb.append(" }\n");
134+
sb.append(" }\n");
135+
136+
return sb.toString();
137+
}
138+
}

0 commit comments

Comments
 (0)