Skip to content
Open
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 @@ -867,6 +867,70 @@ class MiscSingleModuleTest : AbstractGradleTest() {
"Skipping task ':vaadinBuildFrontend' as it is up-to-date") }
}

@Test
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add a test for a plain JAR, since the plugin code allows it.

fun buildFrontendBuildCacheRestoresProductionBundleForWar() {
testProject.buildFile.writeText(
"""
plugins {
id 'war'
id 'org.gretty' version '4.0.3'
id("com.vaadin.flow")
}
repositories {
mavenLocal()
mavenCentral()
maven { url = 'https://maven.vaadin.com/vaadin-prereleases' }
}
dependencies {
implementation("com.vaadin:flow:$flowVersion")
providedCompile("jakarta.servlet:jakarta.servlet-api:6.0.0")
implementation("org.slf4j:slf4j-simple:$slf4jVersion")
}
tasks.register('sourcesJar', Jar) {
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
""".trimIndent()
)

var result = testProject.build("--build-cache", "-Pvaadin.productionMode", "build", "sourcesJar")
result.expectTaskSucceded("vaadinBuildFrontend")
expectArchiveContainsVaadinBundle(testProject.builtWar, false)
expectArchiveDoesntContainVaadinBundle(
testProject.folder("build/libs").find("*-sources.jar").first(),
false
)

File(testProject.dir, "build/vaadin-build-frontend").deleteRecursively()
File(testProject.dir, "build/cached-flow-build-info.json").delete()
File(testProject.dir, "build/libs").deleteRecursively()

result = testProject.build("--build-cache", "-Pvaadin.productionMode", "build")
result.expectTaskOutcome("vaadinBuildFrontend", TaskOutcome.FROM_CACHE)
expectArchiveContainsVaadinBundle(testProject.builtWar, false)
}

@Test
fun buildFrontendBuildCacheRestoresProductionBundleForSpringBootJar() {
doTestSpringProject()

var result = testProject.build(
"--build-cache", "-Pvaadin.productionMode", "bootJar"
)
result.expectTaskSucceded("vaadinBuildFrontend")
expectArchiveContainsVaadinBundle(testProject.builtJar, true)

File(testProject.dir, "build/vaadin-build-frontend").deleteRecursively()
File(testProject.dir, "build/cached-flow-build-info.json").delete()
File(testProject.dir, "build/libs").deleteRecursively()

result = testProject.build(
"--build-cache", "-Pvaadin.productionMode", "bootJar"
)
result.expectTaskOutcome("vaadinBuildFrontend", TaskOutcome.FROM_CACHE)
expectArchiveContainsVaadinBundle(testProject.builtJar, true)
}

@Test
fun buildFrontendIncrementalBuilds_rerunsOnInputChange() {
testProject.buildFile.writeText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ fun expectArchiveContainsVaadinBundle(
val isStandaloneJar: Boolean = !isWar && !isSpringBootJar
val resourcePackaging: String = when {
isWar -> "WEB-INF/classes/"
isSpringBootJar -> "BOOT-INF/classes/"
else -> ""
}
expectArchiveContains(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class VaadinSmokeTest : AbstractGradleTest() {
result.expectTaskNotRan("vaadinPrepareFrontend")
result.expectTaskNotRan("vaadinBuildFrontend")

val build = File(testProject.dir, "build/resources/main/META-INF/VAADIN/webapp/VAADIN/build")
val build = File(testProject.dir, "build/vaadin-build-frontend/META-INF/VAADIN/webapp/VAADIN/build")
expect(false, build.toString()) { build.exists() }
}

Expand All @@ -97,7 +97,7 @@ class VaadinSmokeTest : AbstractGradleTest() {
// vaadinPrepareFrontend
result.expectTaskNotRan("vaadinPrepareFrontend")

val build = File(testProject.dir, "build/resources/main/META-INF/VAADIN/webapp/VAADIN/build")
val build = File(testProject.dir, "build/vaadin-build-frontend/META-INF/VAADIN/webapp/VAADIN/build")
expect(true, build.toString()) { build.isDirectory }
expect(true) { build.listFiles()!!.isNotEmpty() }
build.find("*.br", 4..10)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.vaadin.flow.plugin.base.BuildFrontendUtil
import org.gradle.api.provider.Property
import org.gradle.api.tasks.LocalState
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile

/**
Expand Down Expand Up @@ -52,6 +53,8 @@ internal class BuildFrontendOutputProperties(
VaadinBuildFrontendTask.CACHED_BUILD_INFO_FILE)
private val generatedTsFolder: File =
BuildFrontendUtil.getGeneratedFrontendDirectory(adapter)
private val servletResourceOutputDirectory: File =
adapter.servletResourceOutputDirectory()
private val frontendIndexHtml: File =
File(BuildFrontendUtil.getFrontendDirectory(adapter),
FrontendUtils.INDEX_HTML)
Expand All @@ -63,6 +66,10 @@ internal class BuildFrontendOutputProperties(
@Optional
fun getFrontendIndexHtml(): File = frontendIndexHtml

@OutputDirectory
fun getServletResourceOutputDirectory(): File =
servletResourceOutputDirectory

@LocalState
fun getGeneratedTsFolder(): File = generatedTsFolder
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.War
import org.gradle.util.GradleVersion

/**
Expand Down Expand Up @@ -72,15 +73,37 @@ public class FlowPlugin : Plugin<Project> {
// In production mode, vaadinBuildFrontend is self-contained
// and performs its own frontend preparation, so there is no
// need for vaadinPrepareFrontend to run beforehand.
// this will also catch the War task since it extends from Jar
val buildFrontendTask = project.tasks.getByName("vaadinBuildFrontend")
val buildAdapter = GradlePluginAdapter(buildFrontendTask, config, false)
val vaadinServletResourcesDirectory =
buildAdapter.servletResourceOutputDirectory()
val vaadinBuildFrontendOutputDirectory =
vaadinServletResourcesDirectory.parentFile?.parentFile
Comment thread
mcollovati marked this conversation as resolved.

val sourceSetResourcesDirectory =
project.getBuildResourcesDir(config.sourceSetName.get())

project.tasks.withType(Jar::class.java) { task: Jar ->
task.dependsOn("vaadinBuildFrontend")
// Restore the production token before packaging in
// case it was deleted by a previous build's cleanup.
task.doFirst {
val svc = (project.tasks.getByName("vaadinBuildFrontend")
as VaadinBuildFrontendTask).getTokenService().orNull
svc?.ensureToken()
if (task.isVaadinApplicationArchiveTask()) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this condition needed? Is this to avoid the configuration for sources and Javadoc JARs?
Wouldn't this check potentially break projects that define custom-named JAR tasks or using shadow/fatJAR plugins?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applying the following instruction to all JAR tasks is fundamental to make the plugin work in a backward-compatible way, but the current filter is too restrictive and might break some existing builds.
We should at least add an option to let developers opt-in if they have custom tasks. Or the other way around, apply restrictions for well-know JAR tasks and let the user opt-out custom definitions.

Some options:

  • add a includeFrontendResourceIn(taskName) helper in the Vaadin plugin to opt-in (potentially also add a parameter to specify the target path inside the JAR)
  • filter Jar tasks by classifier (e.g. exlude javadoc, sources, test-sources, etc)
  • add a excludeFrontendResourceIn(taskName) helper

task.dependsOn("vaadinBuildFrontend")
if (vaadinBuildFrontendOutputDirectory != null &&
vaadinBuildFrontendOutputDirectory.canonicalFile !=
sourceSetResourcesDirectory.canonicalFile
) {
task.from(vaadinBuildFrontendOutputDirectory) {
task.vaadinBuildFrontendResourcesArchivePath()
?.let { path ->
it.into(path)
}
}
}
// Restore the production token before packaging in
// case it was deleted by a previous build's cleanup.
task.doFirst {
val svc = (buildFrontendTask
as VaadinBuildFrontendTask).getTokenService().orNull
svc?.ensureToken()
}
}
}
} else if (config.alwaysExecutePrepareFrontend.get()) {
Expand Down Expand Up @@ -149,7 +172,9 @@ public class FlowPlugin : Plugin<Project> {
// all Jar/War packaging tasks have completed.
buildFrontendTask.usesService(tokenService)
project.tasks.withType(Jar::class.java) { task: Jar ->
task.usesService(tokenService)
if (task.isVaadinApplicationArchiveTask()) {
task.usesService(tokenService)
}
}
}
}
Expand All @@ -166,4 +191,21 @@ public class FlowPlugin : Plugin<Project> {
)
}
}

private fun Jar.vaadinBuildFrontendResourcesArchivePath(): String? {
return when {
this is War -> "WEB-INF/classes"
isSpringBootJar() -> "BOOT-INF/classes"
else -> null
}
}

private fun Jar.isVaadinApplicationArchiveTask(): Boolean =
name == JavaPlugin.JAR_TASK_NAME || this is War || isSpringBootJar()

private fun Jar.isSpringBootJar(): Boolean =
generateSequence(javaClass as Class<*>) { it.superclass }
.any {
it.name == "org.springframework.boot.gradle.tasks.bundling.BootJar"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal class GradlePluginAdapter private constructor(

private val projectDir = config.projectDir
private val projectName = config.projectName
private val buildResourcesDir: File =
private val buildResourcesDir =
project.getBuildResourcesDir(config.sourceSetName.get())
private val jarProject: Boolean =
project.tasks.withType(War::class.java).isEmpty()
Expand Down Expand Up @@ -234,14 +234,19 @@ internal class GradlePluginAdapter private constructor(
// generate stuff to build/vaadin-generated.
//
// However, after processResources is done, anything generated into
// build/vaadin-generated would simply be ignored. In such case we therefore
// need to generate stuff directly to build/resources/main.
// build/vaadin-generated would simply be ignored. In such cases,
// production resources must either be generated to the task-owned
// frontend output tree or to the source set resources directory.
if (isBeforeProcessResources) {
return File(
config.resourceOutputDirectory.get(),
Constants.VAADIN_SERVLET_RESOURCES
)
}
val frontendOutputDirectory = frontendOutputDirectory()
if (frontendOutputDirectory.hasVaadinWebappResourcesPath()) {
return frontendOutputDirectory.parentFile
}
return File(buildResourcesDir, Constants.VAADIN_SERVLET_RESOURCES)
}

Expand All @@ -261,6 +266,11 @@ internal class GradlePluginAdapter private constructor(
override fun generateEmbeddableWebComponents(): Boolean =
config.generateEmbeddableWebComponents.get()

private fun File.hasVaadinWebappResourcesPath(): Boolean =
path.replace(File.separatorChar, '/').removeSuffix("/").endsWith(
Constants.VAADIN_WEBAPP_RESOURCES.removeSuffix("/")
)

override fun optimizeBundle(): Boolean = config.optimizeBundle.get()

override fun runNpmInstall(): Boolean = config.runNpmInstall.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public abstract class VaadinFlowPluginExtension @Inject constructor(private val

/**
* The folder where the frontend build tool should output index.js and other generated
* files. Defaults to `null` which will use the auto-detected value of
* resoucesDir of the main SourceSet, usually `build/resources/main/META-INF/VAADIN/webapp/`.
* files. Defaults to `null` which will use a task-owned build directory,
* usually `build/vaadin-build-frontend/META-INF/VAADIN/webapp/`.
*/
@Deprecated(
"use frontendOutputDirectory instead",
Expand All @@ -58,8 +58,8 @@ public abstract class VaadinFlowPluginExtension @Inject constructor(private val

/**
* The folder where the frontend build tool should output index.js and other generated
* files. Defaults to `null` which will use the auto-detected value of
* resoucesDir of the main SourceSet, usually `build/resources/main/META-INF/VAADIN/webapp/`.
* files. Defaults to `null` which will use a task-owned build directory,
* usually `build/vaadin-build-frontend/META-INF/VAADIN/webapp/`.
*/
public abstract val frontendOutputDirectory: Property<File>

Expand Down Expand Up @@ -419,12 +419,14 @@ public class PluginEffectiveConfiguration(
extension.frontendOutputDirectory.convention(
extension.webpackOutputDirectory
.convention(
sourceSetName.map {
File(
project.getBuildResourcesDir(it),
Constants.VAADIN_WEBAPP_RESOURCES
)
}
project.layout.buildDirectory
.dir("vaadin-build-frontend")
.map {
File(
it.asFile,
Constants.VAADIN_WEBAPP_RESOURCES
)
}
)
)

Expand Down
Loading