Skip to content

Commit ad4b515

Browse files
authored
Use shared project config dependency (#1192)
1 parent 7aeb94f commit ad4b515

9 files changed

Lines changed: 759 additions & 151 deletions

File tree

AGENTS.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,50 @@ This pipeline handles:
244244

245245
---
246246

247-
## 8. Backend Parity and Lua Guardrails
247+
## 8. Shared Project Config, Patch Targets, and Run Behavior
248+
249+
Recent Grill/compiler integration work moved `wurst.build` parsing rules into a tiny shared dependency. Keep compiler behavior aligned with that shared model.
250+
251+
### Shared project config dependency
252+
253+
* `de.peeeq.wurstscript/build.gradle` depends on `com.github.wurstscript:wurst-project-config`.
254+
* `de.peeeq.wurstio.languageserver.WurstBuildConfig` is a compiler adapter around the shared model, not a second config DAO.
255+
* Do not duplicate YAML parsing rules, patch aliases, or script-mode behavior in compiler-only code unless it is truly compiler-specific.
256+
* Preserve exact `wc3Patch` names for cache invalidation and diagnostics. Broad patch kind is useful for behavior choices, but not enough for hashes.
257+
258+
### Patch target rules
259+
260+
* Use the shared `Wc3PatchTarget` parser for `wc3Patch`.
261+
* Patch family boundaries:
262+
* below `1.29` => pre-1.29 behavior
263+
* `1.29` through `1.31` => classic
264+
* `1.32+`, `1.36`, `2.0`, and `Reforged-*` => Reforged
265+
* Friendly names and jass-history dump names should resolve through shared config. Do not add one-off aliases in compiler code.
266+
* If jass-history has a broken folder name, fix `wurstscript/jass-history` instead of compensating here.
267+
268+
### Build vs run
269+
270+
* Build/typecheck should prefer pinned `wc3Patch` from `wurst.build` and should not parse the installed Warcraft executable just to decide target patch data.
271+
* Config injection should use the pinned project patch when available, not the locally installed game patch.
272+
* User-facing executable version parsing failures must stay short. Do not print PE parser stack traces unless explicit debug logging is requested.
273+
* Run/launch is different from build: the selected Warcraft executable controls launch arguments and map placement.
274+
* When project patch family and selected client family differ, warn and allow the user to choose a different Warcraft III folder.
275+
* If launch folder selection changes the client, all launch decisions must use that selected `W3InstallationData`, not stale request-level `w3data`.
276+
* Legacy clients that need install-dir map placement must copy to the selected launch install's `Maps/Test` folder.
277+
278+
### Focused tests
279+
280+
For config and run-pipeline changes, prefer these focused checks before broader test runs:
281+
282+
```
283+
./gradlew test --tests tests.wurstscript.tests.WurstBuildConfigTests
284+
./gradlew test --tests tests.wurstscript.tests.MapRequestPatchTargetTests
285+
./gradlew make_for_userdir
286+
```
287+
288+
---
289+
290+
## 9. Backend Parity and Lua Guardrails
248291

249292
Recent fixes established additional rules for backend work. Follow these for all future changes:
250293

@@ -288,7 +331,7 @@ Recent fixes established additional rules for backend work. Follow these for all
288331

289332
---
290333

291-
## 9. Virtual Slot Binding and Determinism (New Generics + Lua)
334+
## 10. Virtual Slot Binding and Determinism (New Generics + Lua)
292335

293336
Recent regressions showed that virtual-slot binding can silently degrade to base/no-op implementations in generated Lua while still compiling. Follow these rules for all related changes:
294337

de.peeeq.wurstscript/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ dependencies {
110110
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
111111
implementation 'com.github.inwc3:jmpq3:e28f6999c0'
112112
implementation 'com.github.inwc3:wc3libs:a69318d921'
113+
implementation 'com.github.wurstscript:wurst-project-config:2c7ccd1a5f'
113114
implementation('com.github.wurstscript:wurstsetup:393cf5ea39') {
114115
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'
115116
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ssh.apache'

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ProjectConfigBuilder.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private static String calculateProjectConfigHash(WurstProjectConfigData projectC
180180
.append("\n");
181181
WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir);
182182
sb.append("scriptMode:").append(buildConfig.scriptMode()).append("\n");
183-
sb.append("wc3Patch:").append(buildConfig.wc3Patch()).append("\n");
183+
sb.append("wc3Patch:").append(buildConfig.wc3PatchName()).append("\n");
184184

185185
return ImportFile.calculateHash(sb.toString().getBytes(StandardCharsets.UTF_8));
186186
} catch (Exception e) {
@@ -200,21 +200,29 @@ private static void applyBuildMapData(WurstProjectConfigData projectConfig, File
200200
try (FileInputStream inputStream = new FileInputStream(mapScript)) {
201201
StringWriter sw = new StringWriter();
202202

203-
if (w3data.getWc3PatchVersion().isPresent()) {
204-
w3I.injectConfigsInJassScript(inputStream, sw, w3data.getWc3PatchVersion().get());
203+
WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir);
204+
GameVersion version = effectiveConfigInjectionVersion(buildDir, w3data);
205+
if (buildConfig.configuredGameVersion().isPresent()) {
206+
WLogger.info("Using wurst.build patch target for map config injection: " + version);
207+
} else if (w3data.getWc3PatchVersion().isPresent()) {
208+
WLogger.info("Using detected game version for map config injection: " + version);
205209
} else {
206-
GameVersion version = buildConfigFromBuildDir(buildDir).fallbackGameVersion();
207-
WLogger.info(
208-
"Failed to determine installed game version. Falling back to wurst.build patch target: " + version
209-
);
210-
w3I.injectConfigsInJassScript(inputStream, sw, version);
210+
WLogger.info("Failed to determine installed game version. Falling back to default patch target: " + version);
211211
}
212+
w3I.injectConfigsInJassScript(inputStream, sw, version);
212213

213214
byte[] scriptBytes = sw.toString().getBytes(StandardCharsets.UTF_8);
214215
Files.write(scriptBytes, result.script);
215216
}
216217
}
217218

219+
private static GameVersion effectiveConfigInjectionVersion(File buildDir, W3InstallationData w3data) {
220+
WurstBuildConfig buildConfig = buildConfigFromBuildDir(buildDir);
221+
return buildConfig.configuredGameVersion()
222+
.or(() -> w3data.getWc3PatchVersion())
223+
.orElseGet(buildConfig::fallbackGameVersion);
224+
}
225+
218226
private static WurstBuildConfig buildConfigFromBuildDir(File buildDir) {
219227
java.nio.file.Path projectRoot = buildDir.toPath().getParent();
220228
if (projectRoot == null) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/WurstBuildConfig.java

Lines changed: 57 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import config.WurstProjectConfigData;
44
import de.peeeq.wurstscript.WLogger;
55
import net.moonlightflower.wc3libs.port.GameVersion;
6+
import org.wurstscript.projectconfig.Wc3PatchTarget;
67

78
import java.io.IOException;
89
import java.lang.reflect.Method;
9-
import java.nio.file.Files;
1010
import java.nio.file.Path;
11-
import java.util.ArrayList;
1211
import java.util.List;
13-
import java.util.Locale;
1412
import java.util.Optional;
1513

1614
import static de.peeeq.wurstio.languageserver.ProjectConfigBuilder.FILE_NAME;
@@ -24,181 +22,133 @@ public enum ScriptMode {
2422

2523
public enum Wc3Patch {
2624
REFORGED,
25+
CLASSIC,
2726
PRE_129
2827
}
2928

30-
private final Optional<ScriptMode> scriptMode;
31-
private final Optional<Wc3Patch> wc3Patch;
29+
private final org.wurstscript.projectconfig.WurstBuildConfig sharedConfig;
3230

33-
private WurstBuildConfig(Optional<ScriptMode> scriptMode, Optional<Wc3Patch> wc3Patch) {
34-
this.scriptMode = scriptMode;
35-
this.wc3Patch = wc3Patch;
31+
private WurstBuildConfig(org.wurstscript.projectconfig.WurstBuildConfig sharedConfig) {
32+
this.sharedConfig = sharedConfig == null ? org.wurstscript.projectconfig.WurstBuildConfig.empty() : sharedConfig;
3633
}
3734

3835
public static WurstBuildConfig empty() {
39-
return new WurstBuildConfig(Optional.empty(), Optional.empty());
36+
return new WurstBuildConfig(org.wurstscript.projectconfig.WurstBuildConfig.empty());
4037
}
4138

4239
public static WurstBuildConfig fromWorkspaceRoot(WFile workspaceRoot) {
40+
if (workspaceRoot == null) {
41+
return empty();
42+
}
4343
return fromBuildFile(Path.of(workspaceRoot.toString(), FILE_NAME));
4444
}
4545

4646
public static WurstBuildConfig fromProject(WurstProjectConfigData projectConfig, WFile workspaceRoot) {
4747
WurstBuildConfig fileConfig = fromWorkspaceRoot(workspaceRoot);
48-
Optional<ScriptMode> scriptMode = readEnumGetter(projectConfig, "getScriptMode", ScriptMode::valueOf)
49-
.or(fileConfig::scriptMode);
50-
Optional<Wc3Patch> wc3Patch = readEnumGetter(projectConfig, "getWc3Patch", WurstBuildConfig::parsePatchName)
51-
.or(fileConfig::wc3Patch);
52-
return new WurstBuildConfig(scriptMode, wc3Patch);
48+
if (projectConfig == null) {
49+
return fileConfig;
50+
}
51+
Optional<org.wurstscript.projectconfig.ScriptMode> scriptMode = readStringGetter(projectConfig, "getScriptMode")
52+
.flatMap(WurstBuildConfig::parseSharedScriptMode)
53+
.or(fileConfig.sharedConfig::scriptMode);
54+
Optional<Wc3PatchTarget> wc3Patch = readStringGetter(projectConfig, "getWc3Patch")
55+
.flatMap(Wc3PatchTarget::parse)
56+
.or(fileConfig.sharedConfig::wc3Patch);
57+
return new WurstBuildConfig(new org.wurstscript.projectconfig.WurstBuildConfig(scriptMode, wc3Patch));
5358
}
5459

5560
static WurstBuildConfig fromBuildFile(Path buildFile) {
56-
if (!Files.exists(buildFile)) {
57-
return empty();
58-
}
59-
Optional<ScriptMode> scriptMode = Optional.empty();
60-
Optional<Wc3Patch> wc3Patch = Optional.empty();
6161
try {
62-
for (String rawLine : Files.readAllLines(buildFile)) {
63-
String line = stripComment(rawLine).trim();
64-
if (line.isEmpty() || Character.isWhitespace(rawLine.charAt(0))) {
65-
continue;
66-
}
67-
int colon = line.indexOf(':');
68-
if (colon < 0) {
69-
continue;
70-
}
71-
String key = line.substring(0, colon).trim();
72-
String value = normalizeScalar(line.substring(colon + 1).trim());
73-
if (key.equals("scriptMode")) {
74-
scriptMode = parseScriptMode(value);
75-
} else if (key.equals("wc3Patch")) {
76-
wc3Patch = parsePatch(value);
77-
}
78-
}
62+
return new WurstBuildConfig(org.wurstscript.projectconfig.WurstBuildConfig.fromBuildFile(buildFile));
7963
} catch (IOException e) {
8064
WLogger.warning("Could not read " + buildFile + " for build settings", e);
65+
return empty();
8166
}
82-
return new WurstBuildConfig(scriptMode, wc3Patch);
8367
}
8468

8569
public Optional<ScriptMode> scriptMode() {
86-
return scriptMode;
70+
return sharedConfig.scriptMode().map(mode -> ScriptMode.valueOf(mode.name()));
8771
}
8872

8973
public Optional<Wc3Patch> wc3Patch() {
90-
return wc3Patch;
74+
return sharedConfig.wc3Patch().map(WurstBuildConfig::patchKind);
75+
}
76+
77+
public Optional<String> wc3PatchName() {
78+
return sharedConfig.wc3Patch().map(Wc3PatchTarget::name);
79+
}
80+
81+
public Optional<GameVersion> configuredGameVersion() {
82+
return sharedConfig.wc3Patch()
83+
.map(Wc3PatchTarget::gameVersion)
84+
.map(GameVersion::new);
9185
}
9286

9387
public Wc3Patch wc3PatchOrReforged() {
94-
return wc3Patch.orElse(Wc3Patch.REFORGED);
88+
return wc3Patch().orElse(Wc3Patch.REFORGED);
9589
}
9690

9791
public GameVersion fallbackGameVersion() {
98-
if (wc3PatchOrReforged() == Wc3Patch.PRE_129) {
99-
return new GameVersion("1.28");
100-
}
101-
return GameVersion.VERSION_1_32;
92+
return configuredGameVersion().orElse(GameVersion.VERSION_1_32);
10293
}
10394

10495
public List<String> applyToCompileArgs(List<String> compileArgs) {
105-
if (!scriptMode.isPresent()) {
106-
return compileArgs;
107-
}
108-
List<String> result = new ArrayList<>();
109-
for (String arg : compileArgs) {
110-
if (!"-lua".equals(arg)) {
111-
result.add(arg);
112-
}
113-
}
114-
if (scriptMode.get() == ScriptMode.LUA) {
115-
result.add("-lua");
116-
}
117-
return result;
96+
return sharedConfig.applyToCompileArgs(compileArgs);
11897
}
11998

12099
public boolean shouldUseReforgedLaunchArgs(Optional<GameVersion> detectedVersion) {
121-
return detectedVersion
122-
.map(version -> version.compareTo(GameVersion.VERSION_1_32) >= 0)
123-
.orElse(wc3PatchOrReforged() == Wc3Patch.REFORGED);
100+
return sharedConfig.shouldUseReforgedLaunchArgs(versionString(detectedVersion));
124101
}
125102

126103
public boolean shouldUseClassicWindowArg(Optional<GameVersion> detectedVersion) {
127-
return detectedVersion
128-
.map(version -> version.compareTo(GameVersion.VERSION_1_31) < 0)
129-
.orElse(wc3PatchOrReforged() == Wc3Patch.PRE_129);
104+
return sharedConfig.shouldUseClassicWindowArg(versionString(detectedVersion));
130105
}
131106

132107
public boolean shouldCopyRunMapToWarcraftMapDir(Optional<GameVersion> detectedVersion) {
133-
return detectedVersion
134-
.map(version -> version.compareTo(GameVersion.VERSION_1_32) < 0)
135-
.orElse(wc3PatchOrReforged() == Wc3Patch.PRE_129);
108+
return sharedConfig.shouldCopyRunMapToWarcraftMapDir(versionString(detectedVersion));
136109
}
137110

138111
public boolean shouldUseInstallDirForMaps(Optional<GameVersion> detectedVersion) {
139-
return detectedVersion.orElseGet(this::fallbackGameVersion)
140-
.compareTo(new GameVersion("1.27.9")) <= 0;
112+
Optional<GameVersion> effectiveVersion = detectedVersion == null ? Optional.empty() : detectedVersion;
113+
return effectiveVersion.or(this::configuredGameVersion)
114+
.map(version -> version.compareTo(new GameVersion("1.27.9")) <= 0)
115+
.orElse(false);
141116
}
142117

143-
private static String stripComment(String line) {
144-
int commentStart = line.indexOf('#');
145-
return commentStart >= 0 ? line.substring(0, commentStart) : line;
146-
}
147-
148-
private static String normalizeScalar(String value) {
149-
String result = value;
150-
if ((result.startsWith("\"") && result.endsWith("\""))
151-
|| (result.startsWith("'") && result.endsWith("'"))) {
152-
result = result.substring(1, result.length() - 1);
153-
}
154-
return result.trim();
118+
private static Optional<String> versionString(Optional<GameVersion> version) {
119+
return version == null ? Optional.empty() : version.map(GameVersion::toString);
155120
}
156121

157-
private static Optional<ScriptMode> parseScriptMode(String value) {
158-
String normalized = value.toUpperCase(Locale.ROOT);
122+
private static Optional<org.wurstscript.projectconfig.ScriptMode> parseSharedScriptMode(String value) {
159123
try {
160-
return Optional.of(ScriptMode.valueOf(normalized));
161-
} catch (IllegalArgumentException e) {
162-
WLogger.warning("Ignoring unknown scriptMode in wurst.build: " + value);
124+
return Optional.of(org.wurstscript.projectconfig.ScriptMode.valueOf(value.trim().toUpperCase()));
125+
} catch (IllegalArgumentException | NullPointerException e) {
163126
return Optional.empty();
164127
}
165128
}
166129

167-
private static Optional<Wc3Patch> parsePatch(String value) {
168-
try {
169-
return Optional.of(parsePatchName(value));
170-
} catch (IllegalArgumentException e) {
171-
WLogger.warning("Ignoring unknown wc3Patch in wurst.build: " + value);
172-
return Optional.empty();
130+
private static Wc3Patch patchKind(Wc3PatchTarget target) {
131+
if (target.kind() == Wc3PatchTarget.Kind.PRE_129) {
132+
return Wc3Patch.PRE_129;
173133
}
174-
}
175-
176-
private static Wc3Patch parsePatchName(String value) {
177-
String normalized = value.toUpperCase(Locale.ROOT)
178-
.replace(".", "_")
179-
.replace("-", "_");
180-
if (normalized.equals("PRE1_29")) {
181-
normalized = "PRE_129";
134+
if (target.kind() == Wc3PatchTarget.Kind.CLASSIC) {
135+
return Wc3Patch.CLASSIC;
182136
}
183-
return Wc3Patch.valueOf(normalized);
184-
}
185-
186-
private interface EnumParser<T> {
187-
T parse(String value);
137+
return Wc3Patch.REFORGED;
188138
}
189139

190-
private static <T> Optional<T> readEnumGetter(WurstProjectConfigData projectConfig, String getterName, EnumParser<T> parser) {
140+
private static Optional<String> readStringGetter(WurstProjectConfigData projectConfig, String getterName) {
191141
try {
192142
Method getter = projectConfig.getClass().getMethod(getterName);
193143
Object value = getter.invoke(projectConfig);
194144
if (value == null) {
195145
return Optional.empty();
196146
}
197-
return Optional.of(parser.parse(value.toString()));
147+
return Optional.of(value.toString());
198148
} catch (NoSuchMethodException ignored) {
199149
return Optional.empty();
200150
} catch (Exception e) {
201-
WLogger.warning("Could not read " + getterName + " from wurst.build config", e);
151+
WLogger.debug("Could not read " + getterName + " from wurst.build config: " + e);
202152
return Optional.empty();
203153
}
204154
}

0 commit comments

Comments
 (0)