GoogleCloudPlatform / app-gradle-plugin

The library has moved to https://github.com/GoogleCloudPlatform/appengine-plugins/tree/main/app-gradle-plugin
Apache License 2.0
153 stars 40 forks source link

Cannot use plugin tasks due to "org.gradle.api.tasks.bundling.Jar.getArchivePath()" because "jar" is null #432

Closed marceloverdijk closed 2 years ago

marceloverdijk commented 2 years ago

I've a minimal Spring Boot application using Java 17 and I'm trying to get AppEngine Gradle plugin to work...

As documented I have set it up like:

settings.gradle:

pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id.startsWith("com.google.cloud.tools.appengine")) {
                useModule("com.google.cloud.tools:appengine-gradle-plugin:${requested.version}")
            }
        }
    }
}

build.gradle:

plugins {
    id "java"
    id "com.google.cloud.tools.appengine-appyaml" version "2.4.4"
    id "io.spring.dependency-management" version "1.0.12.RELEASE"
    id "org.springframework.boot" version "2.6.10"
}

group = "com.github.marceloverdijk"
version = "0.0.1-SNAPSHOT"
sourceCompatibility = "17"

ext {
    set("springCloudVersion", "2021.0.3")
    set("springCloudGcpVersion", "3.3.0")
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        mavenBom "com.google.cloud:spring-cloud-gcp-dependencies:${springCloudGcpVersion}"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

dependencies {
    // implementation "com.google.cloud:spring-cloud-gcp-starter"
    implementation "org.apache.commons:commons-lang3"
    implementation "org.springframework.boot:spring-boot-starter-actuator"
    implementation "org.springframework.boot:spring-boot-starter-jetty"
    implementation("org.springframework.boot:spring-boot-starter-web") {
        exclude module: "spring-boot-starter-tomcat"
    }
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    developmentOnly "org.springframework.boot:spring-boot-devtools"
    testImplementation "org.springframework.boot:spring-boot-starter-test"
}

appengine {
    tools {
                // https://github.com/GoogleCloudPlatform/app-gradle-plugin/issues/431
        cloudSdkHome = "/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk"
    }
    deploy {
        projectId = "GCLOUD_CONFIG"
        version = "GCLOUD_CONFIG"
        promote = true
        stopPreviousVersion = true
    }
}

tasks.named("test") {
    useJUnitPlatform()
}

but when running any GAE tasks I get:

❯ ./gradlew appengineShowConfiguration

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'aircraft-notifier'.
> Cannot invoke "org.gradle.api.tasks.bundling.Jar.getArchivePath()" because "jar" is null

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 587ms

Even the bootRun tasks now fails:

❯ ./gradlew bootRun

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'aircraft-notifier'.
> Cannot invoke "org.gradle.api.tasks.bundling.Jar.getArchivePath()" because "jar" is null

If I remove just the appengine { .. } configuration block from build.gradle (so GAE plugin itself still configured) the app starts up again with bootRun...

marceloverdijk commented 2 years ago

I've removed the appengine plugin completely and was able to deploy the app using gcloud app deploy.

elefeint commented 2 years ago

What version of gradle are you using? This seems related to GoogleCloudPlatform/app-gradle-plugin#382; try running adding jar {} to your configuration.

elefeint commented 2 years ago

Ah, I see from the other issue it's Gradle 7.5. Yes, try adding the empty jar configuration to see if that's the same issue.

marceloverdijk commented 2 years ago

with the empty jar {} I can run appengineShowConfiguration now.

But appengineDeploy then fails later:

❯ ./gradlew appengineDeploy

> Task :appengineDeploy
Services to deploy:

..
target service account:      [App Engine default service account]

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...
.........................................................................................................................failed.
ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build 0026a2d5-195a-4ec3-bfaa-8e818f985d7a status: FAILURE
did not find any jar files with a Main-Class manifest entry
elefeint commented 2 years ago

What if you call ./gradlew clean bootJar appengineDeploy?

I think a thin jar might be getting created.

marceloverdijk commented 2 years ago

./gradlew clean bootJar appengineDeploy gives:

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...
......................................................................................................................failed.
ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build 6f3721f3-a1b4-4942-b6a2-9bc9ef544344 status: FAILURE
did not find any jar files with a Main-Class manifest entry

Maybe this is bc Boot generates 2 jars , 1 normal jar and 1 plain jar. The plain one can be disabled as documented here: https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#packaging-executable.and-plain-archives

So adding:

tasks.named("jar") {
    enabled = false
}

Note I had to add this as well when deploying using Cloud Build (gcloud app deploy).

But then ./gradlew clean bootJar appengineDeploy gives:

❯ ./gradlew clean bootJar appengineDeploy
> Task :appengineStage FAILED

FAILURE: Build failed with an exception.

* What went wrong:
A problem was found with the configuration of task ':appengineStage' (type 'StageAppYamlTask').
  - In plugin 'com.google.cloud.tools.appengine-appyaml' type 'com.google.cloud.tools.gradle.appengine.appyaml.StageAppYamlTask' property 'stagingExtension.artifact' specifies file '/Users/marceloverdijk/workspace/an/build/libs/an-0.0.1-SNAPSHOT-plain.jar' which doesn't exist.

    Reason: An input file was expected to be present but it doesn't exist.

    Possible solutions:
      1. Make sure the file exists before the task is called.
      2. Make sure that the task which produces the file is declared as an input.

    Please refer to https://docs.gradle.org/7.5/userguide/validation_problems.html#input_file_does_not_exist for more details about this problem.
elefeint commented 2 years ago

Ah! Use this appengine configuration: https://github.com/GoogleCloudPlatform/appengine-plugins/issues/1003

marceloverdijk commented 2 years ago

Thx that did the trick!

TWiStErRob commented 1 year ago

If anyone is interested the reason why jar {} worked, is because it forced Gradle to eagerly create and configure (with nothing) the jar Task. This task is used by: https://github.com/GoogleCloudPlatform/app-gradle-plugin/blob/f50be8dcdde14ec6ef94947f1e21f5ea855be792/src/main/java/com/google/cloud/tools/gradle/appengine/appyaml/AppEngineAppYamlPlugin.java#L95-L96

Notice the getProperties().get("jar"), that triggers some magical Groovy path to resolve the property, but since Gradle is lazy, the task is not materialized yet as a property. If this plugin used tasks.named or tasks.getByName directly, then that would look in the right (Task) container and the plugin would not get null.