Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,39 @@ private interface Visitor {
private final Map<String, String> compiledAgainst = new java.util.concurrent.ConcurrentHashMap<>();

Compile(Launch.Project project) {
this(project, null);
}

/**
* Carries what each surviving module was compiled against over from the
* baseline it replaces.
* <p>
* The event that rebuilds this instance - a changed module set - is itself
* a classpath change: a reactor sibling is in the loop only while the
* application depends on it, so dropping that dependency drops the module
* too. Seeding the new baseline from the project as it now stands would
* declare that move already compiled, and the apply would restart the
* application against a classpath its own sources no longer compile against
* - the removed type surfacing as a {@code
* ClassNotFoundException} on the next page load rather than as a diagnostic
* from the apply that caused it. Only the classpath baseline is carried;
* the per-file stamps deliberately start afresh, as they describe a
* different build.
*
* @param project
* the resolved build the new baseline describes
* @param previous
* the baseline this instance replaces, or {@code null} for a
* project's first one
*/
Compile(Launch.Project project, Compile previous) {
this.modules = List.copyOf(project.modules());
this.frontend = Frontend.of(project.app());
for (Reactor.Module module : modules) {
compiledAgainst.put(module.artifactId(),
Launch.membership(project.compileClasspath(module)));
String carried = previous == null ? null
: previous.compiledAgainst.get(module.artifactId());
compiledAgainst.put(module.artifactId(), carried != null ? carried
: Launch.membership(project.compileClasspath(module)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,11 @@ private Compile compileFor(Launch.Project project, Launch.Log log) {
if (current != null) {
log.line("module set changed; re-seeding the change baseline");
}
Compile fresh = new Compile(project);
// The outgoing baseline is handed over so the classpath each module
// was compiled against survives the hand-off: a module set changes
// only because the application gained or lost a reactor dependency,
// and that is the very move the compile leg has to see.
Compile fresh = new Compile(project, current);
// A baseline built while the app is already running must not swallow a
// frontend edit made since it started - that is exactly the "start,
// edit, first apply" sequence, and answering "no changes" to it is the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,50 @@ public class Main { }
assertEquals(List.of("app"), compile.classpathChangedModules(after));
}

@Test
void classpathForced_recompilesWhenAReactorSiblingLeavesTheLoop()
throws IOException {
// Dropping a sibling dependency also drops the sibling from the loop,
// because the module set is read off the application's resolved
// classpath. The baseline is rebuilt for that new module set, and a
// baseline seeded from the project as it now stands would call the
// move already compiled - the apply would then restart the app into a
// ClassNotFoundException instead of failing with a diagnostic.
Reactor.Module app = module("app", "Main", """
package app;
public class Main {
public static String label() {
return shared.Formatter.label();
}
}
""");
Reactor.Module shared = module("shared", "Formatter", """
package shared;
public class Formatter {
public static String label() { return "shared"; }
}
""");
Launch.Project before = reactor(List.of(app, shared),
Map.of("app", List.of(shared)));
Compile previous = new Compile(before);
previous.compile(
List.of(source(app, "Main"), source(shared, "Formatter")),
before);
Launch.Project after = reactor(List.of(app), Map.of());

Compile compile = new Compile(after, previous);

assertEquals(List.of("app"), compile.classpathChangedModules(after));
List<Path> forced = compile.classpathForced(after);
assertEquals(List.of(source(app, "Main")), forced);
Compile.Result result = compile.compile(forced, after);
assertFalse(result.success());
assertTrue(
result.errors().stream()
.anyMatch(error -> error.text().contains("shared")),
() -> "errors: " + result.errors());
}

@Test
void relative_namesASiblingAsTheDeveloperWouldTypeIt() throws IOException {
Reactor.Module app = module("app", "Main", """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ void siblingModuleClassesAreCompiledIntoItsOwnOutput() throws IOException {
// DevLoopBrowserIT asserts that half.
}

@Test
void droppingTheSiblingDependency_failsBeforeAnythingRestarts() {
// The module set is read off the application's resolved classpath, so
// dropping the dependency drops the sibling from the loop too - and
// rebuilding the baseline for that new module set is where the
// classpath move used to be lost. The apply then restarted the
// application into a ClassNotFoundException that only surfaced on the
// next page load, rather than failing here with the diagnostic javac
// already had. Swapped for another jar rather than deleted so the edit
// stays a single line: a multi-line literal would not match a pom
// checked out with CRLF.
patch.replace(APP.resolve("pom.xml"),
"<artifactId>flow-test-devloop-shared</artifactId>",
"<artifactId>flow-webpush</artifactId>");

VaadinDevCli.Outcome outcome = cli.run("apply").assertExitCode(1);

// No source file changed, so javac only ever sees TaskListView - which
// imports the sibling's formatter - because the whole module whose
// classpath moved is recompiled.
outcome.assertOutputContains("TaskListView.java");
outcome.assertOutputDoesNotContain("restarting");
}

@Test
void compileErrorInTheSibling_isNamedByItsModule() {
patch.replace(FORMATTER, "return dueDate == null",
Expand Down
Loading