Class DevLoopBuildExtension

java.lang.Object
org.apache.maven.AbstractMavenLifecycleParticipant
com.vaadin.flow.devloop.mavenext.DevLoopBuildExtension

public class DevLoopBuildExtension extends org.apache.maven.AbstractMavenLifecycleParticipant
Overrides the server plugin's configuration for the run the dev loop drives, without touching the project's pom.

Maven gives a <configuration> value in the pom precedence over the user property the same parameter exposes, so -Djetty.scan=0 on the command line does nothing to a project that writes <scan>2</scan>, which a generated WAR starter does by default. The plugin then redeploys the webapp on a schedule of its own, the second driver of what the transaction model owns: measured, one apply that reported a clean hot swap was followed by the plugin tearing the context down and rebuilding it, losing every page's state for a change that was already live.

Asking every project to write <scan>0</scan> is not a fix - it is a requirement on humans and agents that a tool should not need. A build extension is the supported way to change a model the daemon does not own: it runs inside the application's own build, before any mojo, and edits the effective plugin configuration in memory. Nothing is written to the project.

A second plugin shape needs more than a rewrite: Cargo's run mojo exposes no user property on any parameter that could carry the loop's JVM flags, and reads a Maven project property named cargo.* instead. So the extension also sets project properties for the run, named one at a time by PROPERTY_PREFIX.

A third needs the goal itself. A goal named on a Maven command line runs in every project in the reactor, and TomEE's run mojo has no skip and no packaging check to escape that with - named, it starts a server in the reactor root and blocks the build before the application module is built. Liberty's does start its server in the application's module alone, but runs in the others too and replaces each sibling's jar with its target/classes on the way. A goal bound to a phase runs only where the model carries it, so for that shape the daemon names no goal at all and asks the extension to add the execution here, in the application's module alone; see BIND_PROPERTY.

Both are applied to the one module the daemon names in MODULE_PROPERTY and to no other; see there for why an inherited plugin makes that scope load-bearing rather than tidy.

This class lives in the daemon's jar because that jar's path is the one thing the daemon always knows - it is already its own -javaagent - so putting the extension anywhere else would mean a second artifact to resolve and keep in step. It is compiled against Maven's API at provided scope and is never loaded in the daemon's own JVM; only Maven ever sees it.

For internal use only. May be renamed or removed in a future release.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    A goal to bind in that module, as <phase>:<goal>.
    static final String
    What to force on it, as element=value pairs separated by semicolons.
    static final String
    Where each module's effective model is left, relative to the module's own directory.
    static final String
    artifactId of the module the daemon runs that plugin in.
    static final String
    groupId:artifactId of the plugin to reconfigure.
    static final String
    Prefix of a setting naming a Maven project property to put on the model, as <prefix><name>=<value>.
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    afterProjectsRead(org.apache.maven.execution.MavenSession session)
     

    Methods inherited from class org.apache.maven.AbstractMavenLifecycleParticipant

    afterSessionEnd, afterSessionStart

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • PLUGIN_PROPERTY

      public static final String PLUGIN_PROPERTY
      groupId:artifactId of the plugin to reconfigure.
      See Also:
    • MODULE_PROPERTY

      public static final String MODULE_PROPERTY
      artifactId of the module the daemon runs that plugin in.

      Everything the extension forces is applied to that module and to no other, and the reason is that getBuildPlugins is the effective model: a plugin a reactor parent declares is in every module that inherits it, the reactor root among them. Reconfiguring each one would undo the very thing the reconfiguration is for - Cargo, WildFly and both Payaras are switched off across the reactor by a -D<name>.skip=true and switched back on for the application by a forced <skip>false</skip>, so a forced false everywhere would start a server in the reactor root, or fail there on a packaging that builds no deployment, before the build ever reached the application's own module.

      The artifactId alone, because that is what the daemon already names the module with: the run is -pl :<artifactId> -am, so a reactor in which two modules answered to it is one Maven could not have been asked to run this goal in to begin with.

      See Also:
    • FORCE_PROPERTY

      public static final String FORCE_PROPERTY
      What to force on it, as element=value pairs separated by semicolons. Passed rather than hard-coded so that the values stay in ServerPlugin, where the rest of a server's description lives, and a second container needs no change here.
      See Also:
    • PROPERTY_PREFIX

      public static final String PROPERTY_PREFIX
      Prefix of a setting naming a Maven project property to put on the model, as <prefix><name>=<value>.

      The channel for a plugin whose parameter carries no user property at all, which is the shape Cargo has: every element of its run mojo that could hold the loop's JVM flags is nested and settable from a pom only, while any project property named cargo.* is read and injected as a container configuration property. Setting one is therefore the only way in, and doing it here rather than in the pom leaves the project untouched, exactly as FORCE_PROPERTY does.

      One setting per property, rather than the name=value list FORCE_PROPERTY uses, because there is no separator that would be safe: a value carried this way holds the application's whole JVM command line, and on Windows -Dvaadin.devloop.classes= alone contains semicolons.

      See Also:
    • BIND_PROPERTY

      public static final String BIND_PROPERTY
      A goal to bind in that module, as <phase>:<goal>.

      How a goal is kept to one module when the plugin offers no way of its own. -pl chooses which projects are in the reactor, not which of them a named goal runs in - it runs in all of them - and no command line can say otherwise. A goal bound to a phase runs where the model carries it, and the model is what this extension edits. So the daemon names the phase alone and asks for the goal here.

      Added to the plugin's executions rather than replacing them: the project may have executions of its own, and this run is still that project's build.

      It is given a copy of the plugin's own <configuration>, and that is not a convenience. Maven merges a plugin-level configuration into each execution's while it is building the model, and afterwards consults the plugin-level one for a goal named on the command line alone (DefaultLifecycleExecutionPlanCalculator passes allowPluginLevelConfig only for MojoExecution.Source.CLI). An execution added after the model was built has missed that merge and would run on the mojo's defaults. Measured: <tomeeHttpPort>8892</tomeeHttpPort> and <context>ROOT</context> were both in the effective model and the server still came up on 8080 under the module's finalName.

      Within a phase Maven runs executions in the order their plugins appear in the effective model, and merges the lifecycle-injected ones in first - so war:war is already scheduled ahead of an execution added to a pom-declared plugin at package, which is what the goal needs: it deploys the WAR that phase builds.

      See Also:
    • MODEL_FILE

      public static final String MODEL_FILE
      Where each module's effective model is left, relative to the module's own directory.

      The daemon reads poms with the JDK's XML parser and no Maven at all, which settles most questions but not the two Maven alone can answer: which profiles are active, and what a module inherits from a parent outside the checkout. Both are already decided by the time this runs, so writing them down costs nothing and saves the daemon from guessing.

      Relative to the module and not to ${project.build.directory}, deliberately: the reader is the daemon, which has no Maven to ask where that directory points, so a project that moves it - <build><directory>build</directory></build> - would have the two ends looking at different paths and the model would read as simply missing. target/devloop/ is where the daemon keeps everything else it writes per module, and it is the same path on the reading side; see EffectiveModel#FILE.

      See Also:
  • Constructor Details

    • DevLoopBuildExtension

      public DevLoopBuildExtension()
  • Method Details

    • afterProjectsRead

      public void afterProjectsRead(org.apache.maven.execution.MavenSession session)
      Overrides:
      afterProjectsRead in class org.apache.maven.AbstractMavenLifecycleParticipant