Implement isolated Build and Refresh with immutable leased snapshots
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" ?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>de.neitzel.fxdesigner</groupId>
|
||||
<artifactId>neitzel-fx-designer</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>designer-maven-plugin</artifactId>
|
||||
<packaging>maven-plugin</packaging>
|
||||
<prerequisites><maven>3.9.11</maven></prerequisites>
|
||||
<properties>
|
||||
<module.java.release>21</module.java.release>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-core</artifactId>
|
||||
<version>3.9.11</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>3.9.11</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||
<artifactId>maven-plugin-annotations</artifactId>
|
||||
<version>3.15.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>module-dependency-boundary</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<bannedDependencies>
|
||||
<searchTransitive>true</searchTransitive>
|
||||
<excludes>
|
||||
<exclude>com.intellij:*</exclude>
|
||||
<exclude>com.jetbrains:*</exclude>
|
||||
<exclude>com.jetbrains.intellij.platform:*</exclude>
|
||||
<exclude>com.gluonhq.scenebuilder:*</exclude>
|
||||
<exclude>org.openjfx:*</exclude>
|
||||
<exclude>de.neitzel.lib:*</exclude>
|
||||
</excludes>
|
||||
</bannedDependencies>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-plugin-plugin</artifactId>
|
||||
<version>3.15.1</version>
|
||||
<configuration>
|
||||
<goalPrefix>neitzel-designer</goalPrefix>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,78 @@
|
||||
package de.neitzel.fxdesigner.maven;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.*;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
/** Runs after package in Maven's process; no application class is loaded into a designer host. */
|
||||
@Mojo(name = "capture", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
|
||||
public final class CaptureMojo extends AbstractMojo {
|
||||
@Parameter(defaultValue = "${project}", readonly = true, required = true)
|
||||
private MavenProject project;
|
||||
|
||||
@Parameter(defaultValue = "${session}", readonly = true, required = true)
|
||||
private MavenSession session;
|
||||
|
||||
@Parameter(property = "designer.captureDir", required = true)
|
||||
private java.io.File directory;
|
||||
|
||||
public void execute() throws MojoExecutionException {
|
||||
try {
|
||||
var proof = new Properties();
|
||||
try (var in =
|
||||
Files.newInputStream(
|
||||
directory.toPath().resolve(DependencyProof.id(project) + ".dependencies"))) {
|
||||
proof.load(in);
|
||||
}
|
||||
DependencyProof.verify(proof);
|
||||
var p = new Properties();
|
||||
p.putAll(proof);
|
||||
p.setProperty("format", "2");
|
||||
p.setProperty("module", project.getBasedir().getCanonicalPath());
|
||||
p.setProperty(
|
||||
"coordinates",
|
||||
project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion());
|
||||
p.setProperty("packaging", project.getPackaging());
|
||||
list(p, "runtime", project.getRuntimeClasspathElements());
|
||||
list(p, "test", project.getTestClasspathElements());
|
||||
var roots = new ArrayList<String>(project.getCompileSourceRoots());
|
||||
roots.addAll(project.getTestCompileSourceRoots());
|
||||
project.getResources().forEach(r -> roots.add(r.getDirectory()));
|
||||
project.getTestResources().forEach(r -> roots.add(r.getDirectory()));
|
||||
list(p, "sources", roots);
|
||||
var dependencies = new ArrayList<String>();
|
||||
for (var a : project.getArtifacts())
|
||||
for (var other : session.getProjects())
|
||||
if (a.getGroupId().equals(other.getGroupId())
|
||||
&& a.getArtifactId().equals(other.getArtifactId())
|
||||
&& a.getVersion().equals(other.getVersion()))
|
||||
dependencies.add(other.getBasedir().getCanonicalPath());
|
||||
list(p, "dependencies", dependencies);
|
||||
list(p, "profiles", session.getRequest().getActiveProfiles());
|
||||
Files.createDirectories(directory.toPath());
|
||||
String id =
|
||||
HexFormat.of()
|
||||
.formatHex(
|
||||
java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(
|
||||
project
|
||||
.getBasedir()
|
||||
.getCanonicalPath()
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
|
||||
try (var out = Files.newOutputStream(directory.toPath().resolve(id + ".properties"))) {
|
||||
p.store(out, "Neitzel FX Designer effective Maven capture");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Cannot capture effective Maven metadata", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void list(Properties p, String name, List<String> values) {
|
||||
p.setProperty(name + ".count", Integer.toString(values.size()));
|
||||
for (int i = 0; i < values.size(); i++) p.setProperty(name + "." + i, values.get(i));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.neitzel.fxdesigner.maven;
|
||||
|
||||
import java.nio.file.*;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.*;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
@Mojo(name = "dependencies", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
|
||||
public final class DependenciesMojo extends AbstractMojo {
|
||||
@Parameter(defaultValue = "${project}", readonly = true, required = true)
|
||||
private MavenProject project;
|
||||
|
||||
@Parameter(property = "designer.captureDir", required = true)
|
||||
private java.io.File directory;
|
||||
|
||||
public void execute() throws MojoExecutionException {
|
||||
try {
|
||||
Files.createDirectories(directory.toPath());
|
||||
try (var out =
|
||||
Files.newOutputStream(
|
||||
directory.toPath().resolve(DependencyProof.id(project) + ".dependencies"))) {
|
||||
DependencyProof.capture(project).store(out, "Dependency identities before compilation");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Cannot capture build dependency identities", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package de.neitzel.fxdesigner.maven;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
final class DependencyProof {
|
||||
static String signature(Path path) throws Exception {
|
||||
var a = Files.readAttributes(path, java.nio.file.attribute.BasicFileAttributes.class);
|
||||
var hash = java.security.MessageDigest.getInstance("SHA-256");
|
||||
try (var in = Files.newInputStream(path)) {
|
||||
byte[] b = new byte[65536];
|
||||
for (int n; (n = in.read(b)) != -1; ) hash.update(b, 0, n);
|
||||
}
|
||||
return HexFormat.of().formatHex(hash.digest())
|
||||
+ ":"
|
||||
+ a.size()
|
||||
+ ":"
|
||||
+ a.lastModifiedTime()
|
||||
+ ":"
|
||||
+ a.fileKey();
|
||||
}
|
||||
|
||||
static Properties capture(MavenProject project) throws Exception {
|
||||
var p = new Properties();
|
||||
int n = 0;
|
||||
for (var a : project.getArtifacts()) {
|
||||
Path path = a.getFile().toPath().toRealPath();
|
||||
if (!Files.isRegularFile(path))
|
||||
throw new IllegalArgumentException("Dependency is not a packaged file: " + path);
|
||||
p.setProperty("artifact." + n + ".path", path.toString());
|
||||
p.setProperty("artifact." + n + ".coordinates", a.getId());
|
||||
p.setProperty("artifact." + n + ".signature", signature(path));
|
||||
n++;
|
||||
}
|
||||
p.setProperty("artifact.count", Integer.toString(n));
|
||||
return p;
|
||||
}
|
||||
|
||||
static String id(MavenProject project) throws Exception {
|
||||
return HexFormat.of()
|
||||
.formatHex(
|
||||
java.security.MessageDigest.getInstance("SHA-256")
|
||||
.digest(
|
||||
project
|
||||
.getBasedir()
|
||||
.getCanonicalPath()
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
static void verify(Properties p) throws Exception {
|
||||
int n = Integer.parseInt(p.getProperty("artifact.count"));
|
||||
for (int i = 0; i < n; i++)
|
||||
if (!signature(Path.of(p.getProperty("artifact." + i + ".path")))
|
||||
.equals(p.getProperty("artifact." + i + ".signature")))
|
||||
throw new IllegalStateException("Dependency changed during Maven build");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package de.neitzel.fxdesigner.maven;
|
||||
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.*;
|
||||
|
||||
/** Validates Maven's selected effective projects before any application lifecycle phase. */
|
||||
@Mojo(name = "plan", aggregator = true, threadSafe = true)
|
||||
public final class PlanMojo extends AbstractMojo {
|
||||
@Parameter(defaultValue = "${session}", readonly = true, required = true)
|
||||
private MavenSession session;
|
||||
|
||||
@Parameter(property = "designer.workspace", required = true)
|
||||
private java.io.File workspace;
|
||||
|
||||
public void execute() throws MojoExecutionException {
|
||||
try {
|
||||
Path root = workspace.toPath().toRealPath();
|
||||
var activeProfiles = new HashSet<String>();
|
||||
for (var project : session.getProjects()) {
|
||||
Path base = project.getBasedir().toPath().toRealPath();
|
||||
if (!base.startsWith(root))
|
||||
throw new IllegalArgumentException("Module outside isolated reactor");
|
||||
var paths = new ArrayList<String>();
|
||||
paths.add(project.getBuild().getDirectory());
|
||||
paths.add(project.getBuild().getOutputDirectory());
|
||||
paths.add(project.getBuild().getTestOutputDirectory());
|
||||
paths.addAll(project.getCompileSourceRoots());
|
||||
paths.addAll(project.getTestCompileSourceRoots());
|
||||
project.getResources().forEach(r -> paths.add(r.getDirectory()));
|
||||
project.getTestResources().forEach(r -> paths.add(r.getDirectory()));
|
||||
for (String path : paths)
|
||||
if (!Path.of(path).toAbsolutePath().normalize().startsWith(base))
|
||||
throw new IllegalArgumentException(
|
||||
"Effective source/output path outside module: " + path);
|
||||
project.getActiveProfiles().forEach(p -> activeProfiles.add(p.getId()));
|
||||
project.getInjectedProfileIds().values().forEach(activeProfiles::addAll);
|
||||
getLog().info("Designer scope: " + root.relativize(base) + " (" + project.getId() + ")");
|
||||
}
|
||||
if (!activeProfiles.containsAll(session.getRequest().getActiveProfiles()))
|
||||
throw new IllegalArgumentException("Requested Maven profile did not activate");
|
||||
} catch (Exception e) {
|
||||
throw new MojoExecutionException("Unsupported effective Maven build plan", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user