1

I have a project that consists of two parts: Spring Boot and React. In my Spring Boot build.gradle config I specified what actions should be performed in order to build and run the application.

Here is how it looks like:

plugins {
    id 'org.springframework.boot' version '2.2.5.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
  id "com.github.node-gradle.node" version "2.2.4"
}

group = 'com.vtti'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'org.apache.poi:poi:3.10-FINAL'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
        compile 'org.apache.poi:poi:3.10-FINAL'
        compile 'org.apache.poi:poi-ooxml:3.10-FINAL'
        compile 'com.sendgrid:sendgrid-java:4.1.2'
        compile 'org.json:json:20190722'
        compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.11.0'
}

test {
    useJUnitPlatform()
}

node {
  // Version of node to use.
  version = '10.16.3'
  // Version of Yarn to use.
  yarnVersion = '1.21.1'

  // Base URL for fetching node distributions (change if you have a mirror).
  distBaseUrl = 'https://nodejs.org/dist'

  // If true, it will download node using above parameters.
  // If false, it will try to use globally installed node.
  download = true

  // Set the work directory for unpacking node
  workDir = file("${project.buildDir}/nodejs")
  // Set the work directory for YARN
  yarnWorkDir = file("${project.buildDir}/yarn")
  // Set the work directory where node_modules should be located
  nodeModulesDir = file("${project.projectDir}")
}


task appYarnInstall(type: YarnTask) {
  description = "Install all dependencies from package.json"
  workingDir = file("${project.projectDir}/src/main/client")
  args = ["install"]
}

task appYarnBuild(type: YarnTask) {
  description = "Build production version of the React client"
  workingDir = file("${project.projectDir}/src/main/client")
  args = ["run", "build"]
}

task copyClient(type: Copy) {
  from 'src/main/client/build'
  // into 'build/resources/main/static/.'
  into 'src/main/resources/static/.'
}

appYarnBuild.dependsOn appYarnInstall
copyClient.dependsOn appYarnBuild
compileJava.dependsOn copyClient

When I run gradlew build everything works properly, Gradle performs yarn install, yarn build, etc.

However, when I run gradlew bootRun and want just to compile and run a project, it performs all the yarn actions again and builds a new front-end, which leads in multiple "changed files" that are visible for git and should be committed again.

Is that possible to specify when tasks should be run and run them only on gradlew build and not on gradlew bootRun?

Karen
  • 1,249
  • 4
  • 23
  • 46

3 Answers3

3

Questions like this appear quite often and the answers almost always treat symptoms instead of the actual cause. Most of the solutions will include one of the following Gradle features:

  • The command line option -x or its programmatical equivalent gradle.startParameter.excludedTaskNames
  • Direct access to gradle.taskGraph
  • The onlyIf clause of a task

In most cases, these solutions are actually dirty hacks that may or may not backfire later.

The actual problem in a lot of these cases are tasks that are configured and wired in the wrong way. A common misconception in the Gradle world is the idea that there are only a few tasks that may be called from the command line. Based on this assumption, the whole build script is designed around these tasks (often only the task build from the Java plugin). In these cases, the whole build script consists of dependsOn statements that somehow execute the tasks in the right order when gradle build is called. Your question with its focus on the commands (not the tasks) gradlew build and gradlew bootRun shows the same problem:

Is that possible to specify when tasks should be run and run them only on gradlew build and not on gradlew bootRun?

A much better question to your problem would be:

Is it possible to run the tasks appYarnInstall and appYarnBuild only when they need to run?

As you explained in your question, the actual problem is caused by the fact that the tasks are run again. So, maybe we should figure out when they need to run? If I understand your project structure right, there are two situations where they need to run:

  • If the front-end does not exist at all (e.g. on a fresh checkout)
  • If some front-end source files changed

Now you could implement this logic in your build script, but Gradle provides this out of the box with its incremental build support. You just need to define the inputs and the outputs of tasks that create files and Gradle will figure out when the tasks need to run.

Since I do not fully understand which of your tasks processes what files (and I'm not familiar with the Gradle Node plugin), it is hard for me to give you a fully working build script, but let me instead give you some hints:

  • Do not mix source directories processed by Gradle with source directories processed by an external system like Node or Yarn. The workingDir hack becomes necessary because those tools expect another repository layout. In your current setup, task results end up inside a source directory (src/main/client/build).

  • Do not use a task of type Copy. Instead, define the outputs of your task appYarnBuild and use this task as an additional input of processResources. This also eliminates your task dependency on compileJava.

  • Always store results from your tasks inside buildDir and not in subfolders of src. This way you may always create a clean build using gradle clean build. If tasks create files inside src, they won't be deleted during clean and may cause problems in later builds.

Lukas Körfer
  • 13,515
  • 7
  • 46
  • 62
1

You can exclude a task from being executed using the -x command-line option and providing the name of the task to exclude.

e.g. gradle bootRun -x appYarnInstall

G.Mohr
  • 15
  • 5
0

This question has been asked before, but in your specific case this might be what you're looking for:

gradle.taskGraph.whenReady { graph ->
  if (graph.hasTask(bootRun)) {
    tasks.withType(YarnTask){
      enabled = false
    }
  }
}
dnault
  • 8,340
  • 1
  • 34
  • 53