From 8d3823646870d5f5d1429260b81f38364ea70f6f Mon Sep 17 00:00:00 2001 From: Martin Vysny Date: Wed, 26 Feb 2020 13:21:53 +0200 Subject: [PATCH] Bug/42 circular dependencies (#44) * Make vaadinBuildFrontend run after classes but before jar/war * More thorough tests: assert that WAR/JAR contains Vaadin bundle * Add test for the circular deps bug * Fix war configuration to properly package VAADIN/build stuff * TestUtils: fixed expected location of webpack bundle for Spring-Boot archives * vaadinBuildFrontend now generates webpack bundle to build/resources/main * Make the plugin compatible with Gradle 4.10 * minor * test refactoring * remove unused imports --- .../com/vaadin/gradle/AbstractGradleTest.kt | 50 ++++ .../com/vaadin/gradle/MiscSingleModuleTest.kt | 268 ++++++++++++++++++ .../kotlin/com/vaadin/gradle/TestUtils.kt | 78 ++++- .../com/vaadin/gradle/VaadinSmokeTest.kt | 193 +------------ .../vaadin/gradle/VaadinBuildFrontendTask.kt | 36 +-- .../gradle/VaadinFlowPluginExtension.kt | 22 +- .../kotlin/com/vaadin/gradle/VaadinPlugin.kt | 9 + .../gradle/VaadinPrepareFrontendTask.kt | 27 +- 8 files changed, 441 insertions(+), 242 deletions(-) create mode 100644 src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt create mode 100644 src/functionalTest/kotlin/com/vaadin/gradle/MiscSingleModuleTest.kt diff --git a/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt b/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt new file mode 100644 index 00000000..41416057 --- /dev/null +++ b/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt @@ -0,0 +1,50 @@ +package com.vaadin.gradle + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import java.io.File + +/** + * Prepares a test Gradle project - creates a temp dir for the [testProject] and allow you to run gradle + * tasks. Does not generate `build.gradle` for you + * though - just write the `build.gradle` contents to the [buildFile] instead. + * + * Call [build] to run the Gradle build on the test project. + * @author mavi + */ +abstract class AbstractGradleTest { + + /** + * The testing Gradle project. Automatically deleted after every test. + */ + @Rule + @JvmField + val testProject = TemporaryFolder() + + /** + * The testing Gradle project root. + */ + protected val testProjectDir: File get() = testProject.root + protected val buildFile: File get() = File(testProjectDir, "build.gradle") + + /** + * Runs build on [testProjectDir]; a `build.gradle` [buildFile] is expected + * to be located there. + * + * The function checks that all tasks have succeeded; if not, throws an informative exception. + */ + protected fun build(vararg tasks: String): BuildResult { + val result: BuildResult = GradleRunner.create() + .withProjectDir(testProjectDir) + .withArguments(tasks.toList() + "--stacktrace") + .withPluginClasspath() + .build() + + for (task: String in tasks) { + result.expectTaskSucceded(task) + } + return result + } +} diff --git a/src/functionalTest/kotlin/com/vaadin/gradle/MiscSingleModuleTest.kt b/src/functionalTest/kotlin/com/vaadin/gradle/MiscSingleModuleTest.kt new file mode 100644 index 00000000..56719bc5 --- /dev/null +++ b/src/functionalTest/kotlin/com/vaadin/gradle/MiscSingleModuleTest.kt @@ -0,0 +1,268 @@ +package com.vaadin.gradle + +import org.gradle.testkit.runner.BuildResult +import org.junit.Test +import java.io.File +import java.nio.file.Files +import kotlin.test.expect + +class MiscSingleModuleTest : AbstractGradleTest() { + /** + * Tests https://github.com/vaadin/vaadin-gradle-plugin/issues/26 + */ + @Test + fun testVaadin8Vaadin14MPRProject() { + buildFile.writeText(""" + plugins { + id "com.devsoap.plugin.vaadin" version "1.4.1" + id 'com.vaadin' + } + repositories { + jcenter() + } + // test that we can configure both plugins + vaadin { + version = "8.9.4" + } + vaadin14 { + optimizeBundle = true + } + """) + + // the collision between devsoap's `vaadin` extension and com.vaadin's `vaadin` + // extension would crash even this very simple build. + build("tasks") + } + + /** + * This test covers the [Base Starter Gradle](https://github.com/vaadin/base-starter-gradle) + * example project. + */ + @Test + fun testWarProject() { + buildFile.writeText(""" + plugins { + id 'war' + id 'org.gretty' version '3.0.1' + id("com.vaadin") + } + repositories { + jcenter() + } + vaadin { + optimizeBundle = true + } + dependencies { + // Vaadin 14 + compile("com.vaadin:vaadin-core:14.1.16") { + // Webjars are only needed when running in Vaadin 13 compatibility mode + ["com.vaadin.webjar", "org.webjars.bowergithub.insites", + "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", + "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] + .forEach { group -> exclude(group: group) } + } + providedCompile("javax.servlet:javax.servlet-api:3.1.0") + + // logging + // currently we are logging through the SLF4J API to SLF4J-Simple. See src/main/resources/simplelogger.properties file for the logger configuration + compile("org.slf4j:slf4j-simple:1.7.30") + } + """.trimIndent()) + + val build: BuildResult = build("clean", "vaadinPrepareNode", "vaadinBuildFrontend", "build") + + val war: File = testProjectDir.find("build/libs/*.war").first() + expect(true, "$war is missing\n${build.output}") { war.isFile } + expectArchiveContainsVaadinWebpackBundle(war, false) + } + + /** + * This test covers the https://github.com/mvysny/vaadin14-embedded-jetty-gradle example. + */ + @Test + fun testJarProject() { + buildFile.writeText(""" + plugins { + id 'java' + id("com.vaadin") + } + repositories { + jcenter() + } + def jettyVersion = "9.4.20.v20190813" + vaadin { + optimizeBundle = true + } + dependencies { + // Vaadin 14 + compile("com.vaadin:vaadin-core:14.1.16") { + // Webjars are only needed when running in Vaadin 13 compatibility mode + ["com.vaadin.webjar", "org.webjars.bowergithub.insites", + "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", + "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] + .forEach { group -> exclude(group: group) } + } + + compile("javax.servlet:javax.servlet-api:3.1.0") + compile("org.eclipse.jetty:jetty-continuation:${"$"}{jettyVersion}") + compile("org.eclipse.jetty:jetty-server:${"$"}{jettyVersion}") + compile("org.eclipse.jetty.websocket:websocket-server:${"$"}{jettyVersion}") + compile("org.eclipse.jetty.websocket:javax-websocket-server-impl:${"$"}{jettyVersion}") { + exclude(module: "javax.websocket-client-api") + } + + // logging + // currently we are logging through the SLF4J API to SLF4J-Simple. See src/main/resources/simplelogger.properties file for the logger configuration + compile("org.slf4j:slf4j-simple:1.7.30") + } + """.trimIndent()) + + val build: BuildResult = build("clean", "vaadinBuildFrontend", "build") + + val jar: File = testProjectDir.find("build/libs/*.jar").first() + expect(true, "$jar is missing\n${build.output}") { jar.isFile } + expectArchiveContainsVaadinWebpackBundle(jar, false) + } + + /** + * Tests https://github.com/vaadin/vaadin-gradle-plugin/issues/24 + * + * The `implementation()` dependency type would cause incorrect jar list computation, + * which would then not populate the `node_modules/@vaadin/flow-frontend` folder, + * which would case webpack to fail during vaadinBuildFrontend. + * + * This build script covers the [Spring Boot example](https://github.com/vaadin/base-starter-spring-gradle) + */ + @Test + fun testVaadin14SpringProject() { + buildFile.writeText(""" + plugins { + id 'org.springframework.boot' version '2.2.4.RELEASE' + id 'io.spring.dependency-management' version '1.0.9.RELEASE' + id 'java' + id("com.vaadin") + } + + repositories { + mavenCentral() + } + + ext { + set('vaadinVersion', "14.1.16") + } + + configurations { + developmentOnly + runtimeClasspath { + extendsFrom developmentOnly + } + } + + dependencies { + implementation('com.vaadin:vaadin-spring-boot-starter') { + // Webjars are only needed when running in Vaadin 13 compatibility mode + ["com.vaadin.webjar", "org.webjars.bowergithub.insites", + "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", + "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] + .forEach { group -> exclude(group: group) } + } + developmentOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + } + + dependencyManagement { + imports { + mavenBom "com.vaadin:vaadin-bom:${"$"}{vaadinVersion}" + } + } + """) + + // need to create the Application.java file otherwise bootJar will fail + val appPkg = File(testProjectDir, "src/main/java/com/example/demo") + Files.createDirectories(appPkg.toPath()) + File(appPkg, "DemoApplication.java").writeText(""" + package com.example.demo; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + + @SpringBootApplication + public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + + } + """.trimIndent()) + + val build: BuildResult = build("vaadinPrepareNode", "vaadinBuildFrontend", "build") + + val jar: File = testProjectDir.find("build/libs/*.jar").first() + expect(true, "$jar is missing\n${build.output}") { jar.isFile } + expectArchiveContainsVaadinWebpackBundle(jar, true) + } + + /** + * Tests https://github.com/vaadin/vaadin-gradle-plugin/issues/42 + */ + @Test + fun testCircularDepsBug() { + buildFile.writeText(""" + plugins { + id 'war' + id 'org.gretty' version '3.0.1' + id("com.vaadin") + } + repositories { + jcenter() + } + dependencies { + // Vaadin 14 + compile("com.vaadin:vaadin-core:14.1.16") { + // Webjars are only needed when running in Vaadin 13 compatibility mode + ["com.vaadin.webjar", "org.webjars.bowergithub.insites", + "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", + "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] + .forEach { group -> exclude(group: group) } + } + providedCompile("javax.servlet:javax.servlet-api:3.1.0") + + // logging + // currently we are logging through the SLF4J API to SLF4J-Simple. See src/main/resources/simplelogger.properties file for the logger configuration + compile("org.slf4j:slf4j-simple:1.7.30") + } + + sourceSets { + guiceConfig + } + + configurations { + guiceConfigCompile.extendsFrom compile + } + + dependencies { + // This seems to be a problem with the vaadin-gradle-plugin, but we need this + // to have access to classes of the main sourceSet in the guice sourceSet. + guiceConfigCompile sourceSets.main.output + } + + compileGuiceConfigJava { + options.compilerArgs << "-Xlint:all" + options.compilerArgs << "-Xlint:-serial" + } + + jar { + from sourceSets.guiceConfig.output + } + """.trimIndent()) + + val build: BuildResult = build("clean", "vaadinPrepareNode", "vaadinBuildFrontend", "build") + + val war: File = testProjectDir.find("build/libs/*.war").first() + expect(true, "$war is missing\n${build.output}") { war.isFile } + expectArchiveContainsVaadinWebpackBundle(war, false) + } +} diff --git a/src/functionalTest/kotlin/com/vaadin/gradle/TestUtils.kt b/src/functionalTest/kotlin/com/vaadin/gradle/TestUtils.kt index ae838bac..d40e0ff2 100644 --- a/src/functionalTest/kotlin/com/vaadin/gradle/TestUtils.kt +++ b/src/functionalTest/kotlin/com/vaadin/gradle/TestUtils.kt @@ -21,6 +21,7 @@ import org.gradle.testkit.runner.TaskOutcome import java.io.File import java.nio.file.FileSystems import java.nio.file.PathMatcher +import java.util.zip.ZipInputStream import kotlin.test.expect import kotlin.test.fail @@ -29,7 +30,8 @@ import kotlin.test.fail * @param taskName the name of the task, e.g. `vaadinPrepareNode` */ fun BuildResult.expectTaskSucceded(taskName: String) { - val task: BuildTask = task(":$taskName") ?: fail("Task $taskName was not ran\n$output") + val task: BuildTask = task(":$taskName") + ?: fail("Task $taskName was not ran\n$output") expect(TaskOutcome.SUCCESS, "$taskName did not succeed: ${task.outcome}") { task.outcome } @@ -39,13 +41,81 @@ fun BuildResult.expectTaskSucceded(taskName: String) { * Finds all files matching given [glob] pattern, for example `build/libs/ *.war` * @param expectedCount expected number of files, defaults to 1. */ -fun File.find(glob: String, expectedCount: Int = 1): List { +fun File.find(glob: String, expectedCount: IntRange = 1..1): List { val matcher: PathMatcher = FileSystems.getDefault().getPathMatcher("glob:$absolutePath/$glob") val found: List = absoluteFile.walk() .filter { matcher.matches(it.toPath()) } .toList() - if (found.size != expectedCount) { - fail("Expected $expectedCount $glob but found ${found.size}: $found") + if (found.size !in expectedCount) { + fail("Expected $expectedCount $glob but found ${found.size}: $found . Folder dump: ${absoluteFile.walk().joinToString("\n")}") } return found } + +/** + * Converts glob such as `*.jar` into a Regex which matches such files. + */ +private fun String.globToRegex(): Regex = + Regex(this.replace("?", "[^/]?").replace("*", "[^/]*")) + +/** + * Lists all files in this zip archive, e.g. `META-INF/VAADIN/config/stats.json`. + */ +private fun ZipInputStream.fileNameSequence(): Sequence = + generateSequence { nextEntry?.name } + +/** + * Lists all files in this zip archive, e.g. `META-INF/VAADIN/config/stats.json`. + */ +private fun File.zipListAllFiles(): List = + ZipInputStream(this.inputStream().buffered()).use { zin: ZipInputStream -> + zin.fileNameSequence().toList() + } + +/** + * Expects that given archive contains at least one file matching every glob in the [globs] list. + * @param archiveProvider returns the zip file to examine. + */ +fun expectArchiveContains(vararg globs: String, archiveProvider: () -> File) { + val archive: File = archiveProvider() + val allFiles: List = archive.zipListAllFiles() + + globs.forEach { glob: String -> + val regex: Regex = glob.globToRegex() + val someFileMatch: Boolean = allFiles.any { it.matches(regex) } + expect(true, "No file $glob in $archive, found ${allFiles.joinToString("\n")}") { someFileMatch } + } +} + +/** + * Asserts that given archive (jar/war) contains the Vaadin webpack bundle: + * the `META-INF/VAADIN/build/` directory. + */ +fun expectArchiveContainsVaadinWebpackBundle(archive: File, + isSpringBootJar: Boolean) { + val isWar: Boolean = archive.name.endsWith(".war", true) + val isStandaloneJar: Boolean = !isWar && !isSpringBootJar + val resourcePackaging: String = when { + isWar -> "WEB-INF/classes/" + isSpringBootJar -> "BOOT-INF/classes/" + else -> "" + } + expectArchiveContains( + "${resourcePackaging}META-INF/VAADIN/config/flow-build-info.json", + "${resourcePackaging}META-INF/VAADIN/config/stats.json", + "${resourcePackaging}META-INF/VAADIN/build/*.gz", + "${resourcePackaging}META-INF/VAADIN/build/*.js", + "${resourcePackaging}META-INF/VAADIN/build/webcomponentsjs/webcomponents-*.js", + "${resourcePackaging}META-INF/VAADIN/build/webcomponentsjs/bundles/webcomponents-*.js" + ) { archive } + if (!isStandaloneJar) { + val libPrefix: String = if (isSpringBootJar) "BOOT-INF/lib" else "WEB-INF/lib" + expectArchiveContains("$libPrefix/*.jar") { archive } + } + + // make sure there is only one flow-build-info.json + val allFiles: List = archive.zipListAllFiles() + expect(1, "Multiple flow-build-info.json found: ${allFiles.joinToString("\n")}") { + allFiles.count { it.contains("flow-build-info.json") } + } +} diff --git a/src/functionalTest/kotlin/com/vaadin/gradle/VaadinSmokeTest.kt b/src/functionalTest/kotlin/com/vaadin/gradle/VaadinSmokeTest.kt index dbd7dacf..e7b43e25 100644 --- a/src/functionalTest/kotlin/com/vaadin/gradle/VaadinSmokeTest.kt +++ b/src/functionalTest/kotlin/com/vaadin/gradle/VaadinSmokeTest.kt @@ -16,29 +16,19 @@ package com.vaadin.gradle import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner import org.junit.Before -import org.junit.Rule import org.junit.Test -import org.junit.rules.TemporaryFolder import java.io.File -import java.nio.file.Files import kotlin.test.expect /** + * The most basic tests. If these fail, the plugin is completely broken and all + * other test classes will possibly fail as well. * @author mavi */ -class VaadinSmokeTest { - @Rule @JvmField - val testProject = TemporaryFolder() - - private lateinit var testProjectDir: File - private lateinit var buildFile: File - +class VaadinSmokeTest : AbstractGradleTest() { @Before fun setup() { - testProjectDir = testProject.root - buildFile = File(testProjectDir, "build.gradle") buildFile.writeText(""" plugins { id 'com.vaadin' @@ -86,180 +76,15 @@ class VaadinSmokeTest { fun testBuildFrontend() { val result: BuildResult = build("vaadinPrepareNode", "vaadinBuildFrontend") // vaadinBuildFrontend depends on vaadinPrepareFrontend + // let's explicitly check that vaadinPrepareFrontend has been run result.expectTaskSucceded("vaadinPrepareFrontend") - val build = File(testProjectDir, "build/vaadin-generated/META-INF/VAADIN/build") + val build = File(testProjectDir, "build/resources/main/META-INF/VAADIN/build") expect(true, build.toString()) { build.isDirectory } expect(true) { build.listFiles()!!.isNotEmpty() } - val webcomponentsjs = File(build, "webcomponentsjs") - expect(true, webcomponentsjs.toString()) { webcomponentsjs.isDirectory } - expect(true) { webcomponentsjs.listFiles()!!.isNotEmpty() } - } - - /** - * Tests https://github.com/vaadin/vaadin-gradle-plugin/issues/26 - */ - @Test - fun testVaadin8Vaadin14MPRProject() { - buildFile.writeText(""" - plugins { - id "com.devsoap.plugin.vaadin" version "1.4.1" - id 'com.vaadin' - } - repositories { - jcenter() - } - // test that we can configure both plugins - vaadin { - version = "8.9.4" - } - vaadin14 { - optimizeBundle = true - } - """) - - // the collision between devsoap's `vaadin` extension and com.vaadin's `vaadin` - // extension would crash even this very simple build. - build("tasks") - } - - /** - * This test covers the [Base Starter Gradle](https://github.com/vaadin/base-starter-gradle) - * example project. - */ - @Test - fun testWarProject() { - buildFile.writeText(""" - plugins { - id 'war' - id 'org.gretty' version '3.0.1' - id("com.vaadin") - } - repositories { - jcenter() - } - vaadin { - optimizeBundle = true - } - dependencies { - // Vaadin 14 - compile("com.vaadin:vaadin-core:14.1.16") { - // Webjars are only needed when running in Vaadin 13 compatibility mode - ["com.vaadin.webjar", "org.webjars.bowergithub.insites", - "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", - "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] - .forEach { group -> exclude(group: group) } - } - providedCompile("javax.servlet:javax.servlet-api:3.1.0") - - // logging - // currently we are logging through the SLF4J API to SLF4J-Simple. See src/main/resources/simplelogger.properties file for the logger configuration - compile("org.slf4j:slf4j-simple:1.7.30") - } - """.trimIndent()) - - val build: BuildResult = build("clean", "vaadinPrepareNode", "vaadinBuildFrontend", "build") - - val war: File = testProjectDir.find("build/libs/*.war").first() - expect(true, "$war is missing\n${build.output}") { war.isFile } - } - - /** - * Tests https://github.com/vaadin/vaadin-gradle-plugin/issues/24 - * - * The `implementation()` dependency type would cause incorrect jar list computation, - * which would then not populate the `node_modules/@vaadin/flow-frontend` folder, - * which would case webpack to fail during vaadinBuildFrontend. - * - * This build script covers the [Spring Boot example](https://github.com/vaadin/base-starter-spring-gradle) - */ - @Test - fun testVaadin14SpringProject() { - buildFile.writeText(""" - plugins { - id 'org.springframework.boot' version '2.2.4.RELEASE' - id 'io.spring.dependency-management' version '1.0.9.RELEASE' - id 'java' - id("com.vaadin") - } - - repositories { - mavenCentral() - } - - ext { - set('vaadinVersion', "14.1.16") - } - - configurations { - developmentOnly - runtimeClasspath { - extendsFrom developmentOnly - } - } - - dependencies { - implementation('com.vaadin:vaadin-spring-boot-starter') { - // Webjars are only needed when running in Vaadin 13 compatibility mode - ["com.vaadin.webjar", "org.webjars.bowergithub.insites", - "org.webjars.bowergithub.polymer", "org.webjars.bowergithub.polymerelements", - "org.webjars.bowergithub.vaadin", "org.webjars.bowergithub.webcomponents"] - .forEach { group -> exclude(group: group) } - } - developmentOnly 'org.springframework.boot:spring-boot-devtools' - testImplementation('org.springframework.boot:spring-boot-starter-test') { - exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' - } - } - - dependencyManagement { - imports { - mavenBom "com.vaadin:vaadin-bom:${"$"}{vaadinVersion}" - } - } - """) - - // need to create the Application.java file otherwise bootJar will fail - val appPkg = File(testProjectDir, "src/main/java/com/example/demo") - Files.createDirectories(appPkg.toPath()) - File(appPkg, "DemoApplication.java").writeText(""" - package com.example.demo; - - import org.springframework.boot.SpringApplication; - import org.springframework.boot.autoconfigure.SpringBootApplication; - - @SpringBootApplication - public class DemoApplication { - - public static void main(String[] args) { - SpringApplication.run(DemoApplication.class, args); - } - - } - """.trimIndent()) - - val build: BuildResult = build("vaadinPrepareNode", "vaadinBuildFrontend", "build") - - val jar: File = testProjectDir.find("build/libs/*.jar").first() - expect(true, "$jar is missing\n${build.output}") { jar.isFile } - } - - /** - * Runs build on [testProjectDir]; a `build.gradle` [buildFile] is expected - * to be located there. - * - * The function checks that all tasks have succeeded; if not, throws an informative exception. - */ - private fun build(vararg tasks: String): BuildResult { - val result: BuildResult = GradleRunner.create() - .withProjectDir(testProjectDir) - .withArguments(tasks.toList() + "--stacktrace") - .withPluginClasspath() - .build() - - for (task: String in tasks) { - result.expectTaskSucceded(task) - } - return result + build.find("*.gz", 5..7) + build.find("*.js", 5..7) + build.find("webcomponentsjs/webcomponents-*.js", 2..2) + build.find("webcomponentsjs/bundles/webcomponents-*.js", 4..4) } } diff --git a/src/main/kotlin/com/vaadin/gradle/VaadinBuildFrontendTask.kt b/src/main/kotlin/com/vaadin/gradle/VaadinBuildFrontendTask.kt index 3c3a9f77..e8f71d54 100644 --- a/src/main/kotlin/com/vaadin/gradle/VaadinBuildFrontendTask.kt +++ b/src/main/kotlin/com/vaadin/gradle/VaadinBuildFrontendTask.kt @@ -18,11 +18,9 @@ package com.vaadin.gradle import com.vaadin.flow.server.Constants import com.vaadin.flow.server.frontend.FrontendUtils import com.vaadin.flow.server.frontend.NodeTasks -import elemental.json.JsonObject -import elemental.json.impl.JsonUtil import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.compile.AbstractCompile +import org.gradle.api.tasks.bundling.Jar import java.io.File /** @@ -50,19 +48,14 @@ open class VaadinBuildFrontendTask : DefaultTask() { dependsOn("vaadinPrepareFrontend") // Maven's task run in the LifecyclePhase.PREPARE_PACKAGE phase - // We need to run before 'processResources' which automatically packages - // the outcome of this task for us. - // - // However, we also need access to the produced classes, to be able to analyze e.g. @CssImport annotations used by the project. - // And we can't depend on the 'classes' task since that depends on 'processResources' - // which would create a circular reference. - // - // We will therefore depend on all non-test compile tasks in this "hacky" way. - // See https://stackoverflow.com/questions/27239028/how-to-depend-on-all-compile-and-testcompile-tasks-in-gradle for more info. - dependsOn(project.tasks.withType(AbstractCompile::class.java).matching { !it.name.toLowerCase().contains("test") }) + // We need access to the produced classes, to be able to analyze e.g. + // @CssImport annotations used by the project. + dependsOn("classes") - // Make sure to run this task before the `processResources` task. - project.tasks.named("processResources") { task -> + // Make sure to run this task before the `war`/`jar` tasks, so that + // webpack bundle will end up packaged in the war/jar archive. The inclusion + // rule itself is configured in the VaadinPlugin class. + project.tasks.withType(Jar::class.java) { task: Jar -> task.mustRunAfter("vaadinBuildFrontend") } } @@ -71,19 +64,8 @@ open class VaadinBuildFrontendTask : DefaultTask() { fun vaadinBuildFrontend() { val extension: VaadinFlowPluginExtension = VaadinFlowPluginExtension.get(project) val configFolder = File("${extension.buildOutputDirectory}/META-INF/VAADIN/config") - - // update build file val tokenFile = File(configFolder, "flow-build-info.json") - val json: String = tokenFile.readText() - val buildInfo: JsonObject = JsonUtil.parse(json) - buildInfo.apply { - remove(Constants.NPM_TOKEN) - remove(Constants.GENERATED_TOKEN) - remove(Constants.FRONTEND_TOKEN) - put("productionMode", true) - put(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, false) - } - buildInfo.writeToFile(tokenFile) + check(tokenFile.isFile) { "$tokenFile is missing" } // runNodeUpdater() val jarFiles: Set = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") }.toSet() diff --git a/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt b/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt index d166226f..da312e41 100644 --- a/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt +++ b/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt @@ -18,14 +18,14 @@ package com.vaadin.gradle import com.vaadin.flow.server.Constants import com.vaadin.flow.server.frontend.FrontendUtils import org.gradle.api.Project -import org.gradle.api.tasks.SourceSetContainer import java.io.File open class VaadinFlowPluginExtension(project: Project) { /** * Whether or not we are running in productionMode. */ - var productionMode = false + var productionMode: Boolean = + project.gradle.startParameter.taskNames.contains("vaadinBuildFrontend") /** * The plugin will generate additional resource files here. These files need @@ -34,18 +34,15 @@ open class VaadinFlowPluginExtension(project: Project) { * this as an additional resource folder, which should then be picked up by the IDE. * That will allow the app to run for example in Intellij with Tomcat. * - * For example the `flow-build-info.json` goes here. See [webpackOutputDirectory] - * for more details. + * For example the `flow-build-info.json` goes here. Also see [webpackOutputDirectory]. */ var buildOutputDirectory = File(project.buildDir, "vaadin-generated") /** * The folder where webpack should output index.js and other generated - * files. - * - * In the dev mode, the `flow-build-info.json` file is generated here. + * files. Defaults to `build/resources/main/`. */ - var webpackOutputDirectory = File(buildOutputDirectory, Constants.VAADIN_SERVLET_RESOURCES) + var webpackOutputDirectory = File(File(project.buildDir, "resources/main"), Constants.VAADIN_SERVLET_RESOURCES) /** * The folder where `package.json` file is located. Default is project root @@ -63,7 +60,7 @@ open class VaadinFlowPluginExtension(project: Project) { * template provided by this plugin. Set it to empty string to disable the * feature. */ - var webpackGeneratedTemplate = FrontendUtils.WEBPACK_GENERATED + var webpackGeneratedTemplate: String = FrontendUtils.WEBPACK_GENERATED /** * The folder where flow will put generated files that will be used by * webpack. @@ -109,13 +106,6 @@ open class VaadinFlowPluginExtension(project: Project) { */ val nodeVersion: String = "12.14.1" - init { - project.afterEvaluate { - val sourceSets: SourceSetContainer = it.properties["sourceSets"] as SourceSetContainer - sourceSets.getByName("main").resources.srcDirs(buildOutputDirectory) - } - } - companion object { fun get(project: Project): VaadinFlowPluginExtension = project.extensions.getByType(VaadinFlowPluginExtension::class.java) diff --git a/src/main/kotlin/com/vaadin/gradle/VaadinPlugin.kt b/src/main/kotlin/com/vaadin/gradle/VaadinPlugin.kt index 7c9605fc..3a4e6704 100644 --- a/src/main/kotlin/com/vaadin/gradle/VaadinPlugin.kt +++ b/src/main/kotlin/com/vaadin/gradle/VaadinPlugin.kt @@ -19,6 +19,7 @@ import com.moowork.gradle.node.NodePlugin import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.SourceSetContainer /** * @author mavi @@ -44,5 +45,13 @@ class VaadinPlugin : Plugin { register("vaadinBuildFrontend", VaadinBuildFrontendTask::class.java) register("vaadinPrepareNode", VaadinPrepareNodeTask::class.java) } + + project.afterEvaluate { + val extension: VaadinFlowPluginExtension = VaadinFlowPluginExtension.get(project) + + // add a new source-set folder for generated stuff, by default vaadin-generated + val sourceSets: SourceSetContainer = it.properties["sourceSets"] as SourceSetContainer + sourceSets.getByName("main").resources.srcDirs(extension.buildOutputDirectory) + } } } diff --git a/src/main/kotlin/com/vaadin/gradle/VaadinPrepareFrontendTask.kt b/src/main/kotlin/com/vaadin/gradle/VaadinPrepareFrontendTask.kt index 916904ac..7a3211af 100644 --- a/src/main/kotlin/com/vaadin/gradle/VaadinPrepareFrontendTask.kt +++ b/src/main/kotlin/com/vaadin/gradle/VaadinPrepareFrontendTask.kt @@ -23,7 +23,6 @@ import elemental.json.JsonObject import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.bundling.War -import org.gradle.language.jvm.tasks.ProcessResources import java.io.File import java.nio.file.Files @@ -40,12 +39,7 @@ open class VaadinPrepareFrontendTask : DefaultTask() { // Maven's task run in the LifecyclePhase.PROCESS_RESOURCES phase - // This task generating stuff into build/vaadin-generated/ ; the `processResources` task - // then copies stuff into the build/ folder, which allows the war task to package - // it into the WAR archive. Therefore, make sure to run this task before the `processResources` task. - project.tasks.named("processResources") { task -> - task.mustRunAfter("vaadinPrepareFrontend") - } + project.tasks.getByName("processResources").mustRunAfter("vaadinPrepareFrontend") // if the vaadinPrepareNode task is going to be invoked, it needs to run before this task, // in order to prepare the local copy of node.js @@ -55,7 +49,10 @@ open class VaadinPrepareFrontendTask : DefaultTask() { @TaskAction fun vaadinPrepareFrontend() { val extension: VaadinFlowPluginExtension = VaadinFlowPluginExtension.get(project) + Files.createDirectories(extension.frontendDirectory.toPath()) + Files.createDirectories(extension.buildOutputDirectory.toPath()) + Files.createDirectories(extension.webpackOutputDirectory.toPath()) // propagate build info val configFolder = File("${extension.buildOutputDirectory}/META-INF/VAADIN/config") @@ -63,9 +60,13 @@ open class VaadinPrepareFrontendTask : DefaultTask() { val buildInfo: JsonObject = Json.createObject().apply { put(Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, false) put(Constants.SERVLET_PARAMETER_PRODUCTION_MODE, extension.productionMode) - put(Constants.NPM_TOKEN, extension.npmFolder.absolutePath) - put(Constants.GENERATED_TOKEN, extension.generatedFolder.absolutePath) - put(Constants.FRONTEND_TOKEN, extension.frontendDirectory.absolutePath) + if (extension.productionMode) { + put(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, false) + } else { + put(Constants.NPM_TOKEN, extension.npmFolder.absolutePath) + put(Constants.GENERATED_TOKEN, extension.generatedFolder.absolutePath) + put(Constants.FRONTEND_TOKEN, extension.frontendDirectory.absolutePath) + } } buildInfo.writeToFile(File(configFolder, "flow-build-info.json")) // validateNodeAndNpmVersion() @@ -86,7 +87,11 @@ open class VaadinPrepareFrontendTask : DefaultTask() { // not be able to read files from jar path. val isJarPackaging: Boolean = project.tasks.withType(War::class.java).isEmpty() if (isJarPackaging) { - val jarFiles: Set = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") }.toSet() + val jarFiles: Set = project.configurations + .getByName("runtimeClasspath") + .resolve() + .filter { it.name.endsWith(".jar") } + .toSet() builder.copyResources(jarFiles) }