S8 integrate pinned Palantir formatting through OpenRewrite

This commit is contained in:
2026-10-30 15:50:51 +01:00
parent 4cfd1d71de
commit 327a365550
10 changed files with 483 additions and 15 deletions

131
tooling/format/Format.java Normal file
View File

@@ -0,0 +1,131 @@
import com.palantir.javaformat.java.Formatter;
import com.palantir.javaformat.java.JavaFormatterOptions;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.*;
import org.openrewrite.*;
import org.openrewrite.internal.InMemoryLargeSourceSet;
import org.openrewrite.text.*;
/** Source-launcher bootstrap: no project-specific artifact is resolved or installed. */
public class Format {
public static void main(String[] args) throws Exception {
Path root = Path.of(args[0]).toAbsolutePath().normalize();
boolean check = switch (args[1]) {
case "check" -> true;
case "apply" -> false;
default -> throw new IllegalArgumentException("format.mode must be apply or check");
};
Path cacheFile = root.resolve("target/palantir-cache.properties");
var cache = new Properties();
if (Files.exists(cacheFile)) {
try (var input = Files.newInputStream(cacheFile)) {
cache.load(input);
}
}
String configuration = digest(Files.readString(root.resolve("pom.xml"))
+ Files.readString(root.resolve("tooling/format/Format.java"))
+ System.getProperty("java.version")
+ System.getProperty("java.class.path"));
if (!configuration.equals(cache.getProperty("configuration"))) cache.clear();
cache.setProperty("configuration", configuration);
List<SourceFile> sources = new ArrayList<>();
for (String directory :
List.of("agiledancers-be/src/main/java", "agiledancers-be/src/test/java", "ci", "tooling/format")) {
try (var paths = Files.walk(root.resolve(directory))) {
for (Path path : paths.filter(p -> p.toString().endsWith(".java"))
.sorted()
.toList()) {
try {
String content = Files.readString(path);
String key = root.relativize(path).toString();
if (!digest(content).equals(cache.getProperty(key))) {
sources.add(PlainText.builder()
.sourcePath(root.relativize(path))
.text(content)
.build());
}
} catch (Exception error) {
throw new IllegalStateException(path + ": " + error.getMessage(), error);
}
}
}
}
var context = new InMemoryExecutionContext(error -> {
throw new IllegalStateException("Formatting failed: " + error.getMessage(), error);
});
var results = new PalantirRecipe()
.run(new InMemoryLargeSourceSet(sources), context)
.getChangeset()
.getAllResults();
for (Result result : results) {
System.out.println((check ? "Needs formatting: " : "Formatted: ")
+ result.getAfter().getSourcePath());
}
if (check && !results.isEmpty()) {
throw new IllegalStateException("Run mvn -N exec:exec@format to apply Palantir formatting.");
}
// Complete parsing/formatting before writing any maintained file. Each replacement is atomic.
if (!check) {
for (Result result : results) {
Path destination = root.resolve(result.getAfter().getSourcePath());
Path temporary = Files.createTempFile(destination.getParent(), ".palantir-", ".tmp");
try {
Files.writeString(temporary, result.getAfter().printAll());
Files.setPosixFilePermissions(temporary, Files.getPosixFilePermissions(destination));
Files.move(
temporary,
destination,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(temporary);
}
}
}
for (SourceFile source : sources) {
cache.setProperty(
source.getSourcePath().toString(), digest(Files.readString(root.resolve(source.getSourcePath()))));
}
Files.createDirectories(cacheFile.getParent());
try (var output = Files.newOutputStream(cacheFile)) {
cache.store(output, "Successfully formatted content only");
}
System.out.println(
"Palantir " + args[1] + ": " + sources.size() + " Java files, " + results.size() + " differences");
}
static String digest(String value) throws Exception {
return HexFormat.of()
.formatHex(MessageDigest.getInstance("SHA-256")
.digest(value.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
}
public static final class PalantirRecipe extends Recipe {
public String getDisplayName() {
return "Palantir Java Format";
}
public String getDescription() {
return "Apply the pinned Palantir engine and its native import handling.";
}
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new PlainTextVisitor<ExecutionContext>() {
final Formatter formatter = Formatter.createFormatter(JavaFormatterOptions.builder()
.style(JavaFormatterOptions.Style.PALANTIR)
.formatJavadoc(false)
.build());
@Override
public PlainText visitText(PlainText text, ExecutionContext context) {
try {
return text.withText(formatter.formatSourceAndFixImports(text.getText()));
} catch (Exception | LinkageError error) {
throw new IllegalStateException(text.getSourcePath() + ": " + error, error);
}
}
};
}
}
}

View File

@@ -0,0 +1,31 @@
"""Measure dependency resolution separately; run from the root with network access.
A disposable copy replaces the Java executable with /usr/bin/true, keeping the
pinned exec-plugin dependencies. No formatter/application code executes. The
second invocation uses the populated repository; neither invocation touches the
normal Maven cache. Logs and measurements are written under target/.
"""
from pathlib import Path
import json
import shutil
import subprocess
import tempfile
import time
root = Path(__file__).resolve().parents[2]
(root / "target").mkdir(exist_ok=True)
with tempfile.TemporaryDirectory(prefix="s8-resolution-") as temporary:
fixture = Path(temporary)
shutil.copytree(root / ".mvn", fixture / ".mvn")
pom = (root / "pom.xml").read_text().replace("${java.home}/bin/java", "/usr/bin/true")
(fixture / "pom.xml").write_text(pom)
command = ["mvn", "-N", "-B", "-ntp", "-Dmaven.repo.local=" + str(fixture / "repository"),
"org.codehaus.mojo:exec-maven-plugin:3.6.3:exec@format"]
for label in ("cold-resolution", "warm-resolution"):
start = time.monotonic()
result = subprocess.run(command, cwd=fixture, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
elapsed = time.monotonic() - start
(root / "target" / ("s8-" + label + ".log")).write_text(result.stdout)
print(json.dumps(dict(label=label, seconds=round(elapsed, 3), exit=result.returncode)), flush=True)
if result.returncode:
raise RuntimeError(result.stdout)

View File

@@ -0,0 +1,49 @@
"""Run sequential, warm-cache end-to-end Maven comparisons from the repository root."""
from pathlib import Path
import json
import subprocess
import time
root = Path(__file__).resolve().parents[2]
results = []
pom = root / "pom.xml"
original_pom = pom.read_bytes()
pom_text = original_pom.decode()
start = pom_text.rfind(" <plugin>", 0, pom_text.index("<artifactId>exec-maven-plugin</artifactId>"))
end = pom_text.index(" </plugin>", start) + len(" </plugin>\n")
baseline_pom = (pom_text[:start] + pom_text[end:]).encode()
def run(label, *arguments):
command = ["mvn", "-B", "-ntp", *arguments]
start = time.monotonic()
baseline = "baseline" in label
try:
if baseline:
pom.write_bytes(baseline_pom)
result = subprocess.run(command, cwd=root, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
finally:
pom.write_bytes(original_pom)
elapsed = time.monotonic() - start
(root / "target").mkdir(exist_ok=True)
(root / "target" / ("s8-" + label + ".log")).write_text(result.stdout)
row = dict(label=label, command=command, seconds=round(elapsed, 3), exit=result.returncode)
results.append(row)
print(json.dumps(row), flush=True)
if result.returncode:
raise RuntimeError(result.stdout)
run("warmup", "compile")
run("baseline-warmup", "compile")
for number in range(3):
run("baseline-" + str(number), "compile")
run("formatted-" + str(number), "compile")
source = root / "ci/runner/WaitForDocker.java"
original = source.read_bytes()
try:
source.write_bytes(original + b"\n")
run("changed-file", "compile")
finally:
source.write_bytes(original)
run("clean-baseline", "clean", "verify")
run("clean-formatted", "-Dformat.mode=check", "clean", "verify")
(root / "target/s8-benchmark.json").write_text(json.dumps(results, indent=2) + "\n")

53
tooling/format/test.py Normal file
View File

@@ -0,0 +1,53 @@
"""Integration checks against an isolated checkout; run python3 tooling/format/test.py."""
from pathlib import Path
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
with tempfile.TemporaryDirectory(prefix="palantir-test-") as temporary:
fixture = Path(temporary)
for name in ("pom.xml", ".mvn", "tooling/format"):
source = root / name
destination = fixture / name
if source.is_dir():
shutil.copytree(source, destination)
else:
shutil.copy2(source, destination)
for name in ("agiledancers-be/src/main/java", "agiledancers-be/src/test/java", "ci/runner"):
(fixture / name).mkdir(parents=True)
source = fixture / "ci/runner/Example.java"
original = "import java.util.List;\nclass Example{String text=\"héllo\";int value(){return 1;}}\n"
source.write_text(original)
def run(mode, success=True):
result = subprocess.run(
["mvn", "-N", "-B", "-ntp", "exec:exec@format", "-Dformat.mode=" + mode],
cwd=fixture, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
assert (result.returncode == 0) == success, result.stdout
return result.stdout
output = run("check", False)
assert "ci/runner/Example.java" in output and "mvn -N exec:exec@format" in output
assert source.read_text() == original
run("apply")
formatted = source.read_text()
assert "import java.util.List" not in formatted and "héllo" in formatted
run("check")
run("apply")
assert source.read_text() == formatted
# Force a full (uncached) second pass to prove engine idempotence.
(fixture / "target/palantir-cache.properties").unlink()
run("check")
source.write_text(formatted + "class Broken { ??? }\n")
invalid = source.read_bytes()
output = run("apply", False)
assert "ci/runner/Example.java" in output
assert source.read_bytes() == invalid
# A new file is discovered even after a successful cached check.
source.write_text(formatted)
extra = fixture / "ci/New.java"
extra.write_text("class New{}")
run("check", False)
assert extra.read_text() == "class New{}"
print("PASS: check is read-only, apply/check agree, imports, UTF-8, idempotence, parser errors, new files")