Skip to content

Commit

Permalink
feat: add application identifier to build info (#19669)
Browse files Browse the repository at this point in the history
* feat: add application identifier to build info

Gets or computes an application identifier during production build
and store it into flow-build-info.json

* make applicationIdentifier available through application and deployment configuration

* fix test
  • Loading branch information
mcollovati authored Jul 5, 2024
1 parent 736c476 commit 960bc74
Show file tree
Hide file tree
Showing 16 changed files with 449 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
*/
package com.vaadin.flow.plugin.maven;

import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES;
import static com.vaadin.flow.server.frontend.FrontendUtils.FRONTEND;

import java.io.File;
import java.io.IOException;
import java.net.URI;
Expand All @@ -31,32 +27,34 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.project.MavenProject;

import com.vaadin.flow.plugin.base.BuildFrontendUtil;
import com.vaadin.flow.plugin.base.PluginAdapterBase;
import com.vaadin.flow.server.InitParameters;
import com.vaadin.flow.server.frontend.FrontendTools;
import com.vaadin.flow.server.frontend.installer.NodeInstaller;
import com.vaadin.flow.server.frontend.installer.Platform;
import com.vaadin.flow.server.frontend.scanner.ClassFinder;

import com.vaadin.flow.component.dependency.JavaScript;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.plugin.base.BuildFrontendUtil;
import com.vaadin.flow.plugin.base.PluginAdapterBase;
import com.vaadin.flow.plugin.base.PluginAdapterBuild;
import com.vaadin.flow.server.Constants;
import com.vaadin.flow.server.ExecutionFailedException;
import com.vaadin.flow.server.InitParameters;
import com.vaadin.flow.server.frontend.FrontendTools;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.server.frontend.installer.NodeInstaller;
import com.vaadin.flow.server.frontend.installer.Platform;
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
import com.vaadin.flow.theme.Theme;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES;
import static com.vaadin.flow.server.frontend.FrontendUtils.FRONTEND;

/**
* Goal that builds the dev frontend bundle to be used in Express Build mode.
Expand Down Expand Up @@ -459,4 +457,8 @@ public boolean isReactEnabled() {
return reactEnable;
}

@Override
public String applicationIdentifier() {
return project.getGroupId() + ":" + project.getArtifactId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

package com.vaadin.gradle

import com.vaadin.flow.server.InitParameters
import elemental.json.Json
import org.gradle.testkit.runner.BuildResult
import org.junit.Test
import java.io.File
import java.nio.file.Files
import kotlin.io.path.writeText
import kotlin.test.expect

class MiscMultiModuleTest : AbstractGradleTest() {
Expand Down Expand Up @@ -110,5 +115,112 @@ class MiscMultiModuleTest : AbstractGradleTest() {
expect(null) { b.task(":lib:vaadinBuildFrontend") }
expect(null) { b.task(":vaadinPrepareFrontend") }
expect(null) { b.task(":vaadinBuildFrontend") }

val tokenFile = File(testProject.dir, "web/build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val tokenFileContent = Json.parse(tokenFile.readText())
expect("web") { tokenFileContent.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

@Test
fun `vaadinBuildFrontend application identifier from custom project name`() {
testProject.settingsFile.writeText("""
include 'lib', 'web'
project(':web').name = 'MY_APP_ID'
""".trimIndent())
testProject.buildFile.writeText("""
plugins {
id 'java'
id 'com.vaadin' apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url = 'https://maven.vaadin.com/vaadin-prereleases' }
}
}
project(':lib') {
apply plugin: 'java'
}
""".trimIndent())
testProject.newFolder("lib")
val webFolder = testProject.newFolder("web")
val webBuildFile = Files.createFile(webFolder.toPath().resolve("build.gradle"))
webBuildFile.writeText("""
apply plugin: 'war'
apply plugin: 'com.vaadin'
dependencies {
implementation project(':lib')
implementation("com.vaadin:flow:$flowVersion")
}
vaadin {
nodeAutoUpdate = true // test the vaadin{} block by changing some innocent property with limited side-effect
}
""".trimIndent())

val b: BuildResult = testProject.build("-Pvaadin.productionMode", "vaadinBuildFrontend", checkTasksSuccessful = false)
b.expectTaskSucceded("MY_APP_ID:vaadinPrepareFrontend")
b.expectTaskSucceded("MY_APP_ID:vaadinBuildFrontend")
expect(null) { b.task(":lib:vaadinPrepareFrontend") }
expect(null) { b.task(":lib:vaadinBuildFrontend") }
expect(null) { b.task(":vaadinPrepareFrontend") }
expect(null) { b.task(":vaadinBuildFrontend") }

val tokenFile = File(testProject.dir, "web/build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val tokenFileContent = Json.parse(tokenFile.readText())
expect("MY_APP_ID") { tokenFileContent.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

@Test
fun `vaadinBuildFrontend application identifier from plugin configuration`() {
testProject.settingsFile.writeText("include 'lib', 'web'")
testProject.buildFile.writeText("""
plugins {
id 'java'
id 'com.vaadin' apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url = 'https://maven.vaadin.com/vaadin-prereleases' }
}
}
project(':lib') {
apply plugin: 'java'
}
""".trimIndent())
testProject.newFolder("lib")
val webFolder = testProject.newFolder("web")
val webBuildFile = Files.createFile(webFolder.toPath().resolve("build.gradle"))
webBuildFile.writeText("""
apply plugin: 'war'
apply plugin: 'com.vaadin'
dependencies {
implementation project(':lib')
implementation("com.vaadin:flow:$flowVersion")
}
vaadin {
nodeAutoUpdate = true // test the vaadin{} block by changing some innocent property with limited side-effect
applicationIdentifier = 'MY_APP_ID'
}
""".trimIndent())

val b: BuildResult = testProject.build("-Pvaadin.productionMode", "vaadinBuildFrontend", checkTasksSuccessful = false)
b.expectTaskSucceded("web:vaadinPrepareFrontend")
b.expectTaskSucceded("web:vaadinBuildFrontend")
expect(null) { b.task(":lib:vaadinPrepareFrontend") }
expect(null) { b.task(":lib:vaadinBuildFrontend") }
expect(null) { b.task(":vaadinPrepareFrontend") }
expect(null) { b.task(":vaadinBuildFrontend") }

val tokenFile = File(testProject.dir, "web/build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val tokenFileContent = Json.parse(tokenFile.readText())
expect("MY_APP_ID") { tokenFileContent.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.vaadin.gradle

import com.vaadin.flow.server.InitParameters
import elemental.json.Json
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Test
Expand Down Expand Up @@ -66,6 +68,18 @@ class MiscSingleModuleTest : AbstractGradleTest() {
@Test
fun testWarProjectProductionMode() {
doTestWarProjectProductionMode()
val tokenFile = File(testProject.dir, "build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val tokenFileContent = Json.parse(tokenFile.readText())
expect(testProject.dir.name) { tokenFileContent.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

@Test
fun testWarProjectProductionModeWithCustomName() {
testProject.settingsFile.writeText("rootProject.name = 'my-test-project'")
doTestWarProjectProductionMode()
val tokenFile = File(testProject.dir, "build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val tokenFileContent = Json.parse(tokenFile.readText())
expect("my-test-project") { tokenFileContent.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ class VaadinSmokeTest : AbstractGradleTest() {
val tokenFile = File(testProject.dir, "build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val buildInfo: JsonObject = JsonUtil.parse(tokenFile.readText())
expect(true, buildInfo.toJson()) { buildInfo.getBoolean(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE) }
expect(testProject.dir.name, buildInfo.toJson()) { buildInfo.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

@Test
fun testBuildFrontendInProductionMode_customApplicationIdentifier() {
val result: BuildResult = testProject.build("-Pvaadin.applicationIdentifier=MY_APP_ID", "-Pvaadin.productionMode", "vaadinBuildFrontend", debug = true)
// vaadinBuildFrontend depends on vaadinPrepareFrontend
// let's explicitly check that vaadinPrepareFrontend has been run
result.expectTaskSucceded("vaadinPrepareFrontend")

val tokenFile = File(testProject.dir, "build/resources/main/META-INF/VAADIN/config/flow-build-info.json")
val buildInfo: JsonObject = JsonUtil.parse(tokenFile.readText())
expect("MY_APP_ID", buildInfo.toJson()) { buildInfo.getString(InitParameters.APPLICATION_IDENTIFIER) }
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,6 @@ internal class GradlePluginAdapter(
override fun isPrepareFrontendCacheDisabled(): Boolean = config.alwaysExecutePrepareFrontend.get()

override fun isReactEnabled(): Boolean = config.reactEnable.get()

override fun applicationIdentifier(): String = config.applicationIdentifier.get()
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ internal class PrepareFrontendInputProperties(private val config: PluginEffectiv
@Input
public fun getReactEnable(): Provider<Boolean> = config.reactEnable

@Input
public fun getApplicationIdentifier(): Provider<String> = config.applicationIdentifier

@Input
@Optional
public fun getNodeExecutablePath(): Provider<String> = tools
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ public abstract class VaadinFlowPluginExtension @Inject constructor(private val

public abstract val cleanFrontendFiles: Property<Boolean>

public abstract val applicationIdentifier: Property<String>

public fun filterClasspath(@DelegatesTo(value = ClasspathFilter::class, strategy = Closure.DELEGATE_FIRST) block: Closure<*>) {
block.delegate = classpathFilter
block.resolveStrategy = Closure.DELEGATE_FIRST
Expand All @@ -302,7 +304,7 @@ public class PluginEffectiveConfiguration(
) {
public val productionMode: Provider<Boolean> = extension.productionMode
.convention(false)
.overrideWithSystemProperty("vaadin.productionMode")
.overrideWithSystemPropertyFlag("vaadin.productionMode")

public val sourceSetName: Property<String> = extension.sourceSetName
.convention("main")
Expand Down Expand Up @@ -333,22 +335,22 @@ public class PluginEffectiveConfiguration(

public val pnpmEnable: Provider<Boolean> = extension.pnpmEnable
.convention(Constants.ENABLE_PNPM_DEFAULT)
.overrideWithSystemProperty(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)
.overrideWithSystemPropertyFlag(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)

public val bunEnable: Provider<Boolean> = extension.bunEnable
.convention(Constants.ENABLE_BUN_DEFAULT)
.overrideWithSystemProperty(InitParameters.SERVLET_PARAMETER_ENABLE_BUN)
.overrideWithSystemPropertyFlag(InitParameters.SERVLET_PARAMETER_ENABLE_BUN)

public val useGlobalPnpm: Provider<Boolean> = extension.useGlobalPnpm
.convention(Constants.GLOBAL_PNPM_DEFAULT)
.overrideWithSystemProperty(InitParameters.SERVLET_PARAMETER_GLOBAL_PNPM)
.overrideWithSystemPropertyFlag(InitParameters.SERVLET_PARAMETER_GLOBAL_PNPM)

public val requireHomeNodeExec: Property<Boolean> = extension.requireHomeNodeExec
.convention(false)

public val eagerServerLoad: Provider<Boolean> = extension.eagerServerLoad
.convention(false)
.overrideWithSystemProperty("vaadin.eagerServerLoad")
.overrideWithSystemPropertyFlag("vaadin.eagerServerLoad")

public val applicationProperties: Property<File> = extension.applicationProperties
.convention(File(project.projectDir, "src/main/resources/application.properties"))
Expand Down Expand Up @@ -405,43 +407,59 @@ public class PluginEffectiveConfiguration(

public val frontendHotdeploy: Provider<Boolean> = extension.frontendHotdeploy
.convention(FrontendUtils.isHillaUsed(BuildFrontendUtil.getFrontendDirectory(GradlePluginAdapter(project, this, true))))
.overrideWithSystemProperty(InitParameters.FRONTEND_HOTDEPLOY)
.overrideWithSystemPropertyFlag(InitParameters.FRONTEND_HOTDEPLOY)

public val ciBuild: Provider<Boolean> = extension.ciBuild
.convention(false)
.overrideWithSystemProperty(InitParameters.CI_BUILD)
.overrideWithSystemPropertyFlag(InitParameters.CI_BUILD)

public val skipDevBundleBuild: Property<Boolean> = extension.skipDevBundleBuild
.convention(false)

public val forceProductionBuild: Provider<Boolean> = extension.forceProductionBuild
.convention(false)
.overrideWithSystemProperty(InitParameters.FORCE_PRODUCTION_BUILD)
.overrideWithSystemPropertyFlag(InitParameters.FORCE_PRODUCTION_BUILD)

public val alwaysExecutePrepareFrontend: Property<Boolean> = extension.alwaysExecutePrepareFrontend
.convention(false)

public val reactEnable: Provider<Boolean> = extension.reactEnable
.convention(FrontendUtils.isReactRouterRequired(BuildFrontendUtil.getFrontendDirectory(GradlePluginAdapter(project, this, true))))
.overrideWithSystemProperty(InitParameters.REACT_ENABLE)
.overrideWithSystemPropertyFlag(InitParameters.REACT_ENABLE)

public val cleanFrontendFiles: Property<Boolean> = extension.cleanFrontendFiles
.convention(true)

public val applicationIdentifier: Provider<String> = extension.applicationIdentifier.convention(project.name)
.overrideWithSystemProperty("vaadin.${InitParameters.APPLICATION_IDENTIFIER}")

/**
* Finds the value of a boolean property. It searches in gradle and system properties.
*
* If the property is defined in both gradle and system properties, then the gradle property is taken.
* If the property is defined in both gradle and system properties, then the system property is taken.
*
* @param propertyName the property name
* @return a new provider of the value, which either takes the original value if the system/gradle property is not present,
* `true` if it's defined or if it's set to "true" and `false` otherwise.
*/
private fun Provider<Boolean>.overrideWithSystemProperty(propertyName: String) : Provider<Boolean> = map { originalValue ->
private fun Provider<Boolean>.overrideWithSystemPropertyFlag(propertyName: String) : Provider<Boolean> = map { originalValue ->
project.getBooleanProperty(propertyName) ?: originalValue
}
/**
* Finds the value of a string property. It searches in gradle and system properties.
*
* If the property is defined in both gradle and system properties, then the system property is taken.
*
* @param propertyName the property name
* @return a new provider of the value, which either takes the original value if the system/gradle property is not present.
*/
private fun Provider<String>.overrideWithSystemProperty(propertyName: String) : Provider<String> = map { originalValue ->
project.getStringProperty(propertyName) ?: originalValue
}

override fun toString(): String = "PluginEffectiveConfiguration(" +
"productionMode=${productionMode.get()}, " +
"applicationIdentifier=${applicationIdentifier.get()}, " +
"webpackOutputDirectory=${webpackOutputDirectory.get()}, " +
"npmFolder=${npmFolder.get()}, " +
"frontendDirectory=${frontendDirectory.get()}, " +
Expand Down
Loading

0 comments on commit 960bc74

Please sign in to comment.